blob: 13d6e48c9b4ad035a86385fa3ae4939eafe96f2f [file] [log] [blame]
// 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.common.collect.ImmutableMap;
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.GlobalOrPluginPermission;
import com.google.gerrit.extensions.api.access.PluginPermission;
import java.lang.annotation.Annotation;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Global server permissions built into Gerrit. */
public enum GlobalPermission implements GlobalOrPluginPermission {
ACCESS_DATABASE(GlobalCapability.ACCESS_DATABASE),
ADMINISTRATE_SERVER(GlobalCapability.ADMINISTRATE_SERVER),
CREATE_ACCOUNT(GlobalCapability.CREATE_ACCOUNT),
CREATE_GROUP(GlobalCapability.CREATE_GROUP),
CREATE_PROJECT(GlobalCapability.CREATE_PROJECT),
EMAIL_REVIEWERS(GlobalCapability.EMAIL_REVIEWERS),
FLUSH_CACHES(GlobalCapability.FLUSH_CACHES),
KILL_TASK(GlobalCapability.KILL_TASK),
MAINTAIN_SERVER(GlobalCapability.MAINTAIN_SERVER),
MODIFY_ACCOUNT(GlobalCapability.MODIFY_ACCOUNT),
RUN_AS(GlobalCapability.RUN_AS),
RUN_GC(GlobalCapability.RUN_GC),
STREAM_EVENTS(GlobalCapability.STREAM_EVENTS),
VIEW_ALL_ACCOUNTS(GlobalCapability.VIEW_ALL_ACCOUNTS),
VIEW_CACHES(GlobalCapability.VIEW_CACHES),
VIEW_CONNECTIONS(GlobalCapability.VIEW_CONNECTIONS),
VIEW_PLUGINS(GlobalCapability.VIEW_PLUGINS),
VIEW_QUEUE(GlobalCapability.VIEW_QUEUE);
private static final Logger log = LoggerFactory.getLogger(GlobalPermission.class);
private static final ImmutableMap<String, GlobalPermission> BY_NAME;
static {
ImmutableMap.Builder<String, GlobalPermission> m = ImmutableMap.builder();
for (GlobalPermission p : values()) {
m.put(p.permissionName(), p);
}
BY_NAME = m.build();
}
@Nullable
public static GlobalPermission byName(String name) {
return BY_NAME.get(name);
}
/**
* 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) {
log.error(
"Class {} uses both @{} and @{}",
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 final String name;
GlobalPermission(String name) {
this.name = name;
}
/** @return name used in {@code project.config} permissions. */
@Override
public String permissionName() {
return name;
}
@Override
public String describeForException() {
return toString().toLowerCase(Locale.US).replace('_', ' ');
}
private static GlobalOrPluginPermission resolve(
@Nullable String pluginName,
String capability,
CapabilityScope scope,
boolean fallBackToAdmin,
Class<?> clazz,
Class<?> annotationClass)
throws PermissionBackendException {
if (pluginName != null
&& !"gerrit".equals(pluginName)
&& (scope == CapabilityScope.PLUGIN || scope == CapabilityScope.CONTEXT)) {
return new PluginPermission(pluginName, capability, fallBackToAdmin);
}
if (scope == CapabilityScope.PLUGIN) {
log.error(
"Class {} uses @{}(scope={}), but is not within a plugin",
clazz.getName(),
annotationClass.getSimpleName(),
scope.name());
throw new PermissionBackendException("cannot extract permission");
}
GlobalPermission perm = byName(capability);
if (perm == null) {
log.error("Class {} requires unknown capability {}", clazz.getName(), capability);
throw new PermissionBackendException("cannot extract permission");
}
return perm;
}
@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;
}
}