| // 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 static com.google.gerrit.server.permissions.DefaultPermissionMappings.globalPermission; | |
| import com.google.common.flogger.FluentLogger; | |
| import com.google.gerrit.common.Nullable; | |
| import com.google.gerrit.common.data.GlobalCapability; | |
| import com.google.gerrit.extensions.annotations.CapabilityScope; | |
| import com.google.gerrit.extensions.annotations.RequiresAnyCapability; | |
| import com.google.gerrit.extensions.annotations.RequiresCapability; | |
| import com.google.gerrit.extensions.api.access.GerritPermission; | |
| import com.google.gerrit.extensions.api.access.GlobalOrPluginPermission; | |
| import com.google.gerrit.extensions.api.access.PluginPermission; | |
| import com.google.gerrit.extensions.registration.PluginName; | |
| import java.lang.annotation.Annotation; | |
| import java.util.Collections; | |
| import java.util.LinkedHashSet; | |
| import java.util.Optional; | |
| import java.util.Set; | |
| /** | |
| * Global server permissions built into Gerrit. | |
| * | |
| * <p>See also {@link GlobalCapability} which lists the equivalent strings used in the | |
| * refs/meta/config settings in All-Projects. | |
| */ | |
| public enum GlobalPermission implements GlobalOrPluginPermission { | |
| ACCESS_DATABASE, | |
| ADMINISTRATE_SERVER, | |
| CREATE_ACCOUNT, | |
| CREATE_GROUP, | |
| CREATE_PROJECT, | |
| EMAIL_REVIEWERS, | |
| FLUSH_CACHES, | |
| KILL_TASK, | |
| MAINTAIN_SERVER, | |
| MODIFY_ACCOUNT, | |
| READ_AS, | |
| RUN_AS, | |
| RUN_GC, | |
| STREAM_EVENTS, | |
| VIEW_ACCESS, | |
| VIEW_ALL_ACCOUNTS, | |
| VIEW_CACHES, | |
| VIEW_CONNECTIONS, | |
| VIEW_PLUGINS, | |
| VIEW_QUEUE, | |
| VIEW_SECONDARY_EMAILS; | |
| private static final FluentLogger logger = FluentLogger.forEnclosingClass(); | |
| /** | |
| * Extracts the {@code @RequiresCapability} or {@code @RequiresAnyCapability} annotation. | |
| * | |
| * @param pluginName name of the declaring plugin. May be {@code null} or {@code "gerrit"} for | |
| * classes originating from the core server. | |
| * @param clazz target class to extract annotation from. | |
| * @return empty set if no annotations were found, or a collection of permissions, any of which | |
| * are suitable to enable access. | |
| * @throws PermissionBackendException the annotation could not be parsed. | |
| */ | |
| public static Set<GlobalOrPluginPermission> fromAnnotation( | |
| @Nullable String pluginName, Class<?> clazz) throws PermissionBackendException { | |
| RequiresCapability rc = findAnnotation(clazz, RequiresCapability.class); | |
| RequiresAnyCapability rac = findAnnotation(clazz, RequiresAnyCapability.class); | |
| if (rc != null && rac != null) { | |
| logger.atSevere().log( | |
| "Class %s uses both @%s and @%s", | |
| clazz.getName(), | |
| RequiresCapability.class.getSimpleName(), | |
| RequiresAnyCapability.class.getSimpleName()); | |
| throw new PermissionBackendException("cannot extract permission"); | |
| } else if (rc != null) { | |
| return Collections.singleton( | |
| resolve( | |
| pluginName, | |
| rc.value(), | |
| rc.scope(), | |
| rc.fallBackToAdmin(), | |
| clazz, | |
| RequiresCapability.class)); | |
| } else if (rac != null) { | |
| Set<GlobalOrPluginPermission> r = new LinkedHashSet<>(); | |
| for (String capability : rac.value()) { | |
| r.add( | |
| resolve( | |
| pluginName, | |
| capability, | |
| rac.scope(), | |
| rac.fallBackToAdmin(), | |
| clazz, | |
| RequiresAnyCapability.class)); | |
| } | |
| return Collections.unmodifiableSet(r); | |
| } else { | |
| return Collections.emptySet(); | |
| } | |
| } | |
| public static Set<GlobalOrPluginPermission> fromAnnotation(Class<?> clazz) | |
| throws PermissionBackendException { | |
| return fromAnnotation(null, clazz); | |
| } | |
| private static GlobalOrPluginPermission resolve( | |
| @Nullable String pluginName, | |
| String capability, | |
| CapabilityScope scope, | |
| boolean fallBackToAdmin, | |
| Class<?> clazz, | |
| Class<?> annotationClass) | |
| throws PermissionBackendException { | |
| if (pluginName != null | |
| && !PluginName.GERRIT.equals(pluginName) | |
| && (scope == CapabilityScope.PLUGIN || scope == CapabilityScope.CONTEXT)) { | |
| return new PluginPermission(pluginName, capability, fallBackToAdmin); | |
| } | |
| if (scope == CapabilityScope.PLUGIN) { | |
| logger.atSevere().log( | |
| "Class %s uses @%s(scope=%s), but is not within a plugin", | |
| clazz.getName(), annotationClass.getSimpleName(), scope.name()); | |
| throw new PermissionBackendException("cannot extract permission"); | |
| } | |
| Optional<GlobalPermission> perm = globalPermission(capability); | |
| if (!perm.isPresent()) { | |
| logger.atSevere().log("Class %s requires unknown capability %s", clazz.getName(), capability); | |
| throw new PermissionBackendException("cannot extract permission"); | |
| } | |
| return perm.get(); | |
| } | |
| @Nullable | |
| private static <T extends Annotation> T findAnnotation(Class<?> clazz, Class<T> annotation) { | |
| for (; clazz != null; clazz = clazz.getSuperclass()) { | |
| T t = clazz.getAnnotation(annotation); | |
| if (t != null) { | |
| return t; | |
| } | |
| } | |
| return null; | |
| } | |
| @Override | |
| public String describeForException() { | |
| return GerritPermission.describeEnumValue(this); | |
| } | |
| } |