LCOV - code coverage report
Current view: top level - server/extensions/webui - UiActions.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 60 74 81.1 %
Date: 2022-11-19 15:00:39 Functions: 10 12 83.3 %

          Line data    Source code
       1             : // Copyright (C) 2013 The Android Open Source Project
       2             : //
       3             : // Licensed under the Apache License, Version 2.0 (the "License");
       4             : // you may not use this file except in compliance with the License.
       5             : // You may obtain a copy of the License at
       6             : //
       7             : // http://www.apache.org/licenses/LICENSE-2.0
       8             : //
       9             : // Unless required by applicable law or agreed to in writing, software
      10             : // distributed under the License is distributed on an "AS IS" BASIS,
      11             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12             : // See the License for the specific language governing permissions and
      13             : // limitations under the License.
      14             : 
      15             : package com.google.gerrit.server.extensions.webui;
      16             : 
      17             : import static com.google.gerrit.extensions.conditions.BooleanCondition.and;
      18             : import static com.google.gerrit.extensions.conditions.BooleanCondition.or;
      19             : import static java.util.stream.Collectors.toList;
      20             : 
      21             : import com.google.common.annotations.VisibleForTesting;
      22             : import com.google.common.collect.Streams;
      23             : import com.google.common.flogger.FluentLogger;
      24             : import com.google.gerrit.common.Nullable;
      25             : import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission;
      26             : import com.google.gerrit.extensions.conditions.BooleanCondition;
      27             : import com.google.gerrit.extensions.registration.DynamicMap;
      28             : import com.google.gerrit.extensions.registration.Extension;
      29             : import com.google.gerrit.extensions.registration.PluginName;
      30             : import com.google.gerrit.extensions.restapi.RestCollection;
      31             : import com.google.gerrit.extensions.restapi.RestResource;
      32             : import com.google.gerrit.extensions.restapi.RestView;
      33             : import com.google.gerrit.extensions.webui.PrivateInternals_UiActionDescription;
      34             : import com.google.gerrit.extensions.webui.UiAction;
      35             : import com.google.gerrit.extensions.webui.UiAction.Description;
      36             : import com.google.gerrit.metrics.Description.Units;
      37             : import com.google.gerrit.metrics.Field;
      38             : import com.google.gerrit.metrics.MetricMaker;
      39             : import com.google.gerrit.metrics.Timer1;
      40             : import com.google.gerrit.server.logging.Metadata;
      41             : import com.google.gerrit.server.permissions.GlobalPermission;
      42             : import com.google.gerrit.server.permissions.PermissionBackend;
      43             : import com.google.gerrit.server.permissions.PermissionBackendCondition;
      44             : import com.google.gerrit.server.permissions.PermissionBackendException;
      45             : import com.google.inject.Inject;
      46             : import com.google.inject.Singleton;
      47             : import java.util.HashMap;
      48             : import java.util.Iterator;
      49             : import java.util.List;
      50             : import java.util.Map;
      51             : import java.util.Objects;
      52             : import java.util.Set;
      53             : import java.util.function.Predicate;
      54             : 
      55             : @Singleton
      56             : public class UiActions {
      57         148 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      58             : 
      59             :   public static Predicate<UiAction.Description> enabled() {
      60           0 :     return UiAction.Description::isEnabled;
      61             :   }
      62             : 
      63             :   private final PermissionBackend permissionBackend;
      64             :   private final Timer1<String> uiActionLatency;
      65             : 
      66             :   @Inject
      67         148 :   UiActions(PermissionBackend permissionBackend, MetricMaker metricMaker) {
      68         148 :     this.permissionBackend = permissionBackend;
      69         148 :     this.uiActionLatency =
      70         148 :         metricMaker.newTimer(
      71             :             "http/server/rest_api/ui_actions/latency",
      72             :             new com.google.gerrit.metrics.Description("Latency for RestView#getDescription calls")
      73         148 :                 .setCumulative()
      74         148 :                 .setUnit(Units.MILLISECONDS),
      75         148 :             Field.ofString("view", Metadata.Builder::restViewName)
      76         148 :                 .description("view implementation class")
      77         148 :                 .build());
      78         148 :   }
      79             : 
      80             :   public <R extends RestResource> Iterable<UiAction.Description> from(
      81             :       RestCollection<?, R> collection, R resource) {
      82           0 :     return from(collection.views(), resource);
      83             :   }
      84             : 
      85             :   public <R extends RestResource> Iterable<UiAction.Description> from(
      86             :       DynamicMap<RestView<R>> views, R resource) {
      87          66 :     List<UiAction.Description> descs =
      88          66 :         Streams.stream(views)
      89          66 :             .map(e -> describe(e, resource))
      90          66 :             .filter(Objects::nonNull)
      91          66 :             .collect(toList());
      92             : 
      93          66 :     List<PermissionBackendCondition> conds =
      94          66 :         Streams.concat(
      95          66 :                 descs.stream().flatMap(u -> Streams.stream(visibleCondition(u))),
      96          66 :                 descs.stream().flatMap(u -> Streams.stream(enabledCondition(u))))
      97          66 :             .collect(toList());
      98             : 
      99          66 :     evaluatePermissionBackendConditions(permissionBackend, conds);
     100             : 
     101          66 :     return descs.stream().filter(Description::isVisible).collect(toList());
     102             :   }
     103             : 
     104             :   @VisibleForTesting
     105             :   static void evaluatePermissionBackendConditions(
     106             :       PermissionBackend perm, List<PermissionBackendCondition> conds) {
     107          66 :     Map<PermissionBackendCondition, PermissionBackendCondition> dedupedConds =
     108          66 :         new HashMap<>(conds.size());
     109          66 :     for (PermissionBackendCondition cond : conds) {
     110          57 :       dedupedConds.put(cond, cond);
     111          57 :     }
     112          66 :     perm.bulkEvaluateTest(dedupedConds.keySet());
     113          66 :     for (PermissionBackendCondition cond : conds) {
     114          57 :       cond.set(dedupedConds.get(cond).value());
     115          57 :     }
     116          66 :   }
     117             : 
     118             :   private static Iterable<PermissionBackendCondition> visibleCondition(Description u) {
     119          61 :     return u.getVisibleCondition().reduce().children(PermissionBackendCondition.class);
     120             :   }
     121             : 
     122             :   private static Iterable<PermissionBackendCondition> enabledCondition(Description u) {
     123          61 :     return u.getEnabledCondition().reduce().children(PermissionBackendCondition.class);
     124             :   }
     125             : 
     126             :   @Nullable
     127             :   private <R extends RestResource> UiAction.Description describe(
     128             :       Extension<RestView<R>> e, R resource) {
     129          66 :     int d = e.getExportName().indexOf('.');
     130          66 :     if (d < 0) {
     131           0 :       return null;
     132             :     }
     133             : 
     134             :     RestView<R> view;
     135             :     try {
     136          66 :       view = e.getProvider().get();
     137           0 :     } catch (RuntimeException err) {
     138           0 :       logger.atSevere().withCause(err).log(
     139           0 :           "error creating view %s.%s", e.getPluginName(), e.getExportName());
     140           0 :       return null;
     141          66 :     }
     142             : 
     143          66 :     if (!(view instanceof UiAction)) {
     144          66 :       return null;
     145             :     }
     146             : 
     147          61 :     String name = e.getExportName().substring(d + 1);
     148          61 :     UiAction.Description dsc = null;
     149          61 :     try (Timer1.Context<String> ignored = uiActionLatency.start(name)) {
     150          61 :       dsc = ((UiAction<R>) view).getDescription(resource);
     151           0 :     } catch (Exception ex) {
     152           0 :       logger.atSevere().withCause(ex).log("Unable to render UIAction. Will omit from actions");
     153          61 :     }
     154          61 :     if (dsc == null) {
     155          57 :       return null;
     156             :     }
     157             : 
     158             :     Set<GlobalOrPluginPermission> globalRequired;
     159             :     try {
     160          61 :       globalRequired = GlobalPermission.fromAnnotation(e.getPluginName(), view.getClass());
     161           0 :     } catch (PermissionBackendException err) {
     162           0 :       logger.atSevere().withCause(err).log(
     163           0 :           "exception testing view %s.%s", e.getPluginName(), e.getExportName());
     164           0 :       return null;
     165          61 :     }
     166          61 :     if (!globalRequired.isEmpty()) {
     167          22 :       PermissionBackend.WithUser withUser = permissionBackend.currentUser();
     168          22 :       Iterator<GlobalOrPluginPermission> i = globalRequired.iterator();
     169          22 :       BooleanCondition p = withUser.testCond(i.next());
     170          22 :       while (i.hasNext()) {
     171           0 :         p = or(p, withUser.testCond(i.next()));
     172             :       }
     173          22 :       dsc.setVisible(and(p, dsc.getVisibleCondition()));
     174             :     }
     175             : 
     176          61 :     PrivateInternals_UiActionDescription.setMethod(dsc, e.getExportName().substring(0, d));
     177          61 :     PrivateInternals_UiActionDescription.setId(
     178          61 :         dsc, PluginName.GERRIT.equals(e.getPluginName()) ? name : e.getPluginName() + '~' + name);
     179          61 :     return dsc;
     180             :   }
     181             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750