org.apache.solr.security.RuleBasedAuthorizationPluginBase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of solr-core Show documentation
Show all versions of solr-core Show documentation
Apache Solr (module: core)
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.solr.security;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;
import static org.apache.solr.handler.admin.SecurityConfHandler.getListValue;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.solr.api.AnnotatedApi;
import org.apache.solr.api.Api;
import org.apache.solr.common.SpecProvider;
import org.apache.solr.common.util.CommandOperation;
import org.apache.solr.common.util.ValidatingJsonMap;
import org.apache.solr.handler.admin.api.ModifyRuleBasedAuthConfigAPI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** Base class for rule based authorization plugins */
public abstract class RuleBasedAuthorizationPluginBase
implements AuthorizationPlugin, ConfigEditablePlugin, SpecProvider {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final Map mapping = new HashMap<>();
private final Map> roleToPermissionsMap = new HashMap<>();
// Doesn't implement Map because we violate the contracts of put() and get()
private static class WildCardSupportMap {
final Set wildcardPrefixes = new HashSet<>();
final Map> delegate = new HashMap<>();
public List put(String key, List value) {
if (key != null && key.endsWith("/*")) {
key = key.substring(0, key.length() - 2);
wildcardPrefixes.add(key);
}
return delegate.put(key, value);
}
public List get(String key) {
List result = delegate.get(key);
if (key == null || result != null) return result;
for (String s : wildcardPrefixes) {
if (key.startsWith(s)) {
List wildcardPermissions = delegate.get(s);
if (wildcardPermissions != null) {
if (result == null) result = new ArrayList<>();
result.addAll(wildcardPermissions);
}
}
}
return result;
}
public Set keySet() {
return delegate.keySet();
}
}
@Override
public AuthorizationResponse authorize(AuthorizationContext context) {
List collectionRequests =
context.getCollectionRequests();
if (log.isDebugEnabled()) {
log.debug(
"Attempting to authorize request to [{}] of type: [{}], associated with collections [{}]",
context.getResource(),
context.getRequestType(),
collectionRequests);
}
if (context.getRequestType() == AuthorizationContext.RequestType.ADMIN) {
log.debug("Authorizing an ADMIN request, checking admin permissions");
MatchStatus flag = checkCollPerm(mapping.get(null), context);
return flag.rsp;
}
for (AuthorizationContext.CollectionRequest collreq : collectionRequests) {
// check permissions for each collection
log.debug(
"Authorizing collection-aware request, checking perms applicable to specific collection [{}]",
collreq.collectionName);
MatchStatus flag = checkCollPerm(mapping.get(collreq.collectionName), context);
if (flag != MatchStatus.NO_PERMISSIONS_FOUND) return flag.rsp;
}
log.debug(
"Authorizing collection-aware request, checking perms applicable to all (*) collections");
// check wildcard (all=*) permissions.
MatchStatus flag = checkCollPerm(mapping.get("*"), context);
return flag.rsp;
}
/**
* Retrieves permission names for a given set of roles.
*
* There are two special role names that can be used in the roles list:
*
*
* null
meaning permission granted for all requests, even without a role
* "*"
meaning any role will grant the permission
*
*
* In order to obtain all permissions a user has based on the user's roles, you also need to
* include these two special roles to get the full list.
*
* @param roles a collection of role names.
*/
public Set getPermissionNamesForRoles(Collection roles) {
if (roles == null) {
return Set.of();
}
return roles.stream()
.filter(roleToPermissionsMap::containsKey)
.flatMap(r -> roleToPermissionsMap.get(r).stream())
.map(p -> p.name)
.collect(Collectors.toSet());
}
private MatchStatus checkCollPerm(WildCardSupportMap pathVsPerms, AuthorizationContext context) {
if (pathVsPerms == null) return MatchStatus.NO_PERMISSIONS_FOUND;
if (log.isTraceEnabled()) {
log.trace("Following perms are associated with collection");
for (String pathKey : pathVsPerms.keySet()) {
final List permsAssociatedWithPath = pathVsPerms.get(pathKey);
log.trace("Path: [{}], Perms: [{}]", pathKey, permsAssociatedWithPath);
}
}
String path = context.getResource();
MatchStatus flag = checkPathPerm(pathVsPerms.get(path), context);
if (flag != MatchStatus.NO_PERMISSIONS_FOUND) return flag;
return checkPathPerm(pathVsPerms.get(null), context);
}
private MatchStatus checkPathPerm(List permissions, AuthorizationContext context) {
if (permissions == null || permissions.isEmpty()) {
return MatchStatus.NO_PERMISSIONS_FOUND;
}
log.trace("Following perms are associated with this collection and path: [{}]", permissions);
final Permission governingPermission = findFirstGoverningPermission(permissions, context);
if (governingPermission == null) {
if (log.isDebugEnabled()) {
log.debug(
"No perms configured for the resource {} . So allowed to access",
context.getResource());
}
return MatchStatus.NO_PERMISSIONS_FOUND;
}
if (log.isDebugEnabled()) {
log.debug(
"Found perm [{}] to govern resource [{}]", governingPermission, context.getResource());
}
return determineIfPermissionPermitsPrincipal(context, governingPermission);
}
private Permission findFirstGoverningPermission(
List permissions, AuthorizationContext context) {
for (int i = 0; i < permissions.size(); i++) {
Permission permission = permissions.get(i);
if (permissionAppliesToRequest(permission, context)) return permission;
}
return null;
}
private boolean permissionAppliesToRequest(Permission permission, AuthorizationContext context) {
if (log.isTraceEnabled()) {
log.trace(
"Testing whether permission [{}] applies to request [{}]",
permission,
context.getResource());
}
if (PermissionNameProvider.values.containsKey(permission.name)) {
return predefinedPermissionAppliesToRequest(permission, context);
} else {
return customPermissionAppliesToRequest(permission, context);
}
}
private boolean predefinedPermissionAppliesToRequest(
Permission predefinedPermission, AuthorizationContext context) {
log.trace("Permission [{}] is a predefined perm", predefinedPermission);
if (predefinedPermission.wellknownName == PermissionNameProvider.Name.ALL) {
log.trace("'ALL' perm applies to all requests; perm applies.");
return true; // 'ALL' applies to everything!
} else if (!(context.getHandler() instanceof PermissionNameProvider)) {
// TODO: Is this code path needed anymore, now that all handlers implement the interface?
// context.getHandler returns Object and is not documented
if (log.isTraceEnabled()) {
log.trace(
"Request handler [{}] is not a PermissionNameProvider, perm doesnt apply",
context.getHandler());
}
// We're not 'ALL', and the handler isn't associated with any other predefined permissions
return false;
} else {
PermissionNameProvider handler = (PermissionNameProvider) context.getHandler();
PermissionNameProvider.Name permissionName = handler.getPermissionName(context);
boolean applies =
permissionName != null && predefinedPermission.name.equals(permissionName.name);
log.trace(
"Request handler [{}] is associated with predefined perm [{}]? {}",
handler,
predefinedPermission.name,
applies);
return applies;
}
}
private boolean customPermissionAppliesToRequest(
Permission customPermission, AuthorizationContext context) {
log.trace("Permission [{}] is a custom permission", customPermission);
if (customPermission.method != null
&& !customPermission.method.contains(context.getHttpMethod())) {
if (log.isTraceEnabled()) {
log.trace(
"Custom permission requires method [{}] but request had method [{}]; permission doesn't apply",
customPermission.method,
context.getHttpMethod());
}
// this permissions HTTP method does not match this rule. try other rules
return false;
}
if (customPermission.params != null) {
for (Map.Entry> e : customPermission.params.entrySet()) {
String[] paramVal = context.getParams().getParams(e.getKey());
if (!e.getValue().apply(paramVal)) {
if (log.isTraceEnabled()) {
log.trace(
"Request has param [{}] which is incompatible with custom perm [{}]; perm doesnt apply",
e.getKey(),
customPermission);
}
return false;
}
}
}
log.trace(
"Perm [{}] matches method and params for request; permission applies", customPermission);
return true;
}
private MatchStatus determineIfPermissionPermitsPrincipal(
AuthorizationContext context, Permission governingPermission) {
if (governingPermission.role == null) {
log.debug("Governing permission [{}] has no role; permitting access", governingPermission);
return MatchStatus.PERMITTED;
}
Principal principal = context.getUserPrincipal();
if (principal == null) {
log.debug(
"Governing permission [{}] has role, but request principal cannot be identified; forbidding access",
governingPermission);
return MatchStatus.USER_REQUIRED;
} else if (governingPermission.role.contains("*")) {
log.debug(
"Governing permission [{}] allows all roles; permitting access", governingPermission);
return MatchStatus.PERMITTED;
}
Set userRoles = getUserRoles(context);
for (String role : governingPermission.role) {
if (userRoles != null && userRoles.contains(role)) {
log.debug(
"Governing permission [{}] allows access to role [{}]; permitting access",
governingPermission,
role);
return MatchStatus.PERMITTED;
}
}
log.info(
"This resource is configured to have a permission {}, The principal {} does not have the right role ",
governingPermission,
principal);
return MatchStatus.FORBIDDEN;
}
public boolean doesUserHavePermission(
Principal principal, PermissionNameProvider.Name permission) {
Set roles = getUserRoles(principal);
if (roles != null) {
for (String role : roles) {
if (mapping.get(null) == null) continue;
List permissions = mapping.get(null).get(null);
if (permissions != null) {
for (Permission p : permissions) {
if (permission.equals(p.wellknownName) && p.role.contains(role)) {
return true;
}
}
}
}
}
return false;
}
@Override
@SuppressWarnings({"unchecked"})
public void init(Map initInfo) {
mapping.put(null, new WildCardSupportMap());
List