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 : }