mirror of https://github.com/apache/lucene.git
SOLR-7838: An authorizationPlugin interface where the access control rules are stored/managed in ZooKeeper
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1694553 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
db2ccb1d41
commit
d841e40a7e
|
@ -0,0 +1,449 @@
|
||||||
|
package org.apache.solr.security;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import org.apache.solr.common.SolrException;
|
||||||
|
import org.apache.solr.common.params.CollectionParams;
|
||||||
|
import org.apache.solr.common.util.Utils;
|
||||||
|
import org.apache.solr.util.CommandOperation;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import static org.apache.solr.handler.admin.SecurityConfHandler.getMapValue;
|
||||||
|
import static org.apache.solr.common.params.CommonParams.NAME;
|
||||||
|
import static org.apache.solr.common.util.Utils.getDeepCopy;
|
||||||
|
|
||||||
|
|
||||||
|
public class RuleBasedAuthorizationPlugin implements AuthorizationPlugin, ConfigEditablePlugin {
|
||||||
|
static final Logger log = LoggerFactory.getLogger(RuleBasedAuthorizationPlugin.class);
|
||||||
|
|
||||||
|
private final Map<String, Set<String>> usersVsRoles = new HashMap<>();
|
||||||
|
private final Map<String, WildCardSupportMap> mapping = new HashMap<>();
|
||||||
|
private final List<Permission> permissions = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
private static class WildCardSupportMap extends HashMap<String, List<Permission>> {
|
||||||
|
final Set<String> wildcardPrefixes = new HashSet<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Permission> put(String key, List<Permission> value) {
|
||||||
|
if (key != null && key.endsWith("/*")) {
|
||||||
|
key = key.substring(0, key.length() - 2);
|
||||||
|
wildcardPrefixes.add(key);
|
||||||
|
}
|
||||||
|
return super.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Permission> get(Object key) {
|
||||||
|
List<Permission> result = super.get(key);
|
||||||
|
if (key == null || result != null) return result;
|
||||||
|
if (!wildcardPrefixes.isEmpty()) {
|
||||||
|
for (String s : wildcardPrefixes) {
|
||||||
|
if (key.toString().startsWith(s)) {
|
||||||
|
List<Permission> l = super.get(s);
|
||||||
|
if (l != null) {
|
||||||
|
result = result == null ? new ArrayList<>() : new ArrayList<>(result);
|
||||||
|
result.addAll(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthorizationResponse authorize(AuthorizationContext context) {
|
||||||
|
List<AuthorizationContext.CollectionRequest> collectionRequests = context.getCollectionRequests();
|
||||||
|
if (collectionRequests != null) {
|
||||||
|
for (AuthorizationContext.CollectionRequest collreq : collectionRequests) {
|
||||||
|
//check permissions for each collection
|
||||||
|
MatchStatus flag = checkCollPerm(mapping.get(collreq.collectionName), context);
|
||||||
|
if (flag != MatchStatus.NO_PERMISSIONS_FOUND) return flag.rsp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//check global permissions.
|
||||||
|
MatchStatus flag = checkCollPerm(mapping.get(null), context);
|
||||||
|
return flag.rsp;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MatchStatus checkCollPerm(Map<String, List<Permission>> pathVsPerms,
|
||||||
|
AuthorizationContext context) {
|
||||||
|
if (pathVsPerms == null) return MatchStatus.NO_PERMISSIONS_FOUND;
|
||||||
|
|
||||||
|
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<Permission> permissions, AuthorizationContext context) {
|
||||||
|
if (permissions == null || permissions.isEmpty()) return MatchStatus.NO_PERMISSIONS_FOUND;
|
||||||
|
Principal principal = context.getUserPrincipal();
|
||||||
|
loopPermissions:
|
||||||
|
for (int i = 0; i < permissions.size(); i++) {
|
||||||
|
Permission permission = permissions.get(i);
|
||||||
|
if (permission.method != null && !permission.method.contains(context.getHttpMethod())) {
|
||||||
|
//this permissions HTTP method does not match this rule. try other rules
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(permission.predicate != null){
|
||||||
|
if(!permission.predicate.test(context)) continue ;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permission.params != null) {
|
||||||
|
for (Map.Entry<String, Object> e : permission.params.entrySet()) {
|
||||||
|
String paramVal = context.getParams().get(e.getKey());
|
||||||
|
Object val = e.getValue();
|
||||||
|
if (val instanceof List) {
|
||||||
|
if (!((List) val).contains(paramVal)) continue loopPermissions;
|
||||||
|
} else if (!Objects.equals(val, paramVal)) continue loopPermissions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permission.role == null) {
|
||||||
|
//no role is assigned permission.That means everybody is allowed to access
|
||||||
|
return MatchStatus.PERMITTED;
|
||||||
|
}
|
||||||
|
if (principal == null) {
|
||||||
|
//this resource needs a principal but the request has come without
|
||||||
|
//any credential.
|
||||||
|
return MatchStatus.USER_REQUIRED;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String role : permission.role) {
|
||||||
|
Set<String> userRoles = usersVsRoles.get(principal.getName());
|
||||||
|
if (userRoles != null && userRoles.contains(role)) return MatchStatus.PERMITTED;
|
||||||
|
}
|
||||||
|
return MatchStatus.FORBIDDEN;
|
||||||
|
}
|
||||||
|
return MatchStatus.NO_PERMISSIONS_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Map<String, Object> initInfo) {
|
||||||
|
mapping.put(null, new WildCardSupportMap());
|
||||||
|
Map<String, Object> map = getMapValue(initInfo, "user-role");
|
||||||
|
for (Object o : map.entrySet()) {
|
||||||
|
Map.Entry e = (Map.Entry) o;
|
||||||
|
String roleName = (String) e.getKey();
|
||||||
|
usersVsRoles.put(roleName, readValueAsSet(map, roleName));
|
||||||
|
}
|
||||||
|
map = getMapValue(initInfo, "permissions");
|
||||||
|
for (Object o : map.entrySet()) {
|
||||||
|
Map.Entry e = (Map.Entry) o;
|
||||||
|
Permission p;
|
||||||
|
try {
|
||||||
|
p = Permission.load((String) e.getKey(), (Map) e.getValue());
|
||||||
|
} catch (Exception exp) {
|
||||||
|
log.error("Invalid permission ", exp);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
permissions.add(p);
|
||||||
|
add2Mapping(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add2Mapping(Permission permission) {
|
||||||
|
//this is to do optimized lookup of permissions for a given collection/path
|
||||||
|
for (String c : permission.collections) {
|
||||||
|
WildCardSupportMap m = mapping.get(c);
|
||||||
|
if (m == null) mapping.put(c, m = new WildCardSupportMap());
|
||||||
|
for (String path : permission.path) {
|
||||||
|
List<Permission> perms = m.get(path);
|
||||||
|
if (perms == null) m.put(path, perms = new ArrayList<>());
|
||||||
|
perms.add(permission);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* read a key value as a set. if the value is a single string ,
|
||||||
|
* return a singleton set
|
||||||
|
*
|
||||||
|
* @param m the map from which to lookup
|
||||||
|
* @param key the key with which to do lookup
|
||||||
|
*/
|
||||||
|
static Set<String> readValueAsSet(Map m, String key) {
|
||||||
|
Set<String> result = new HashSet<>();
|
||||||
|
Object val = m.get(key);
|
||||||
|
if (val == null) return null;
|
||||||
|
if (val instanceof Collection) {
|
||||||
|
Collection list = (Collection) val;
|
||||||
|
for (Object o : list) result.add(String.valueOf(o));
|
||||||
|
} else if (val instanceof String) {
|
||||||
|
result.add((String) val);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Bad value for : " + key);
|
||||||
|
}
|
||||||
|
return result.isEmpty() ? null : Collections.unmodifiableSet(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException { }
|
||||||
|
|
||||||
|
static class Permission {
|
||||||
|
String name;
|
||||||
|
Set<String> path, role, collections, method;
|
||||||
|
Map<String, Object> params;
|
||||||
|
Predicate<AuthorizationContext> predicate;
|
||||||
|
|
||||||
|
private Permission() {
|
||||||
|
}
|
||||||
|
|
||||||
|
static Permission load(String name, Map m) {
|
||||||
|
Permission p = new Permission();
|
||||||
|
if (!m.containsKey("role")) throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "role not specified");
|
||||||
|
p.role = readValueAsSet(m, "role");
|
||||||
|
if (well_known_permissions.containsKey(name)) {
|
||||||
|
HashSet<String> disAllowed = new HashSet<>(knownKeys);
|
||||||
|
disAllowed.remove("role");
|
||||||
|
for (String s : disAllowed) {
|
||||||
|
if (m.containsKey(s))
|
||||||
|
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, s + " is not a valid key for the permission : " + name);
|
||||||
|
}
|
||||||
|
p.predicate = (Predicate<AuthorizationContext>) ((Map) well_known_permissions.get(name)).get(Predicate.class.getName());
|
||||||
|
m = well_known_permissions.get(name);
|
||||||
|
}
|
||||||
|
p.name = name;
|
||||||
|
p.path = readSetSmart(name, m, "path");
|
||||||
|
p.collections = readSetSmart(name, m, "collection");
|
||||||
|
p.method = readSetSmart(name, m, "method");
|
||||||
|
p.params = (Map<String, Object>) m.get("params");
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final Set<String> knownKeys = ImmutableSet.of("collection", "role", "params", "path", "method");
|
||||||
|
}
|
||||||
|
|
||||||
|
enum MatchStatus {
|
||||||
|
USER_REQUIRED(AuthorizationResponse.PROMPT),
|
||||||
|
NO_PERMISSIONS_FOUND(AuthorizationResponse.OK),
|
||||||
|
PERMITTED(AuthorizationResponse.OK),
|
||||||
|
FORBIDDEN(AuthorizationResponse.FORBIDDEN);
|
||||||
|
|
||||||
|
final AuthorizationResponse rsp;
|
||||||
|
|
||||||
|
MatchStatus(AuthorizationResponse rsp) {
|
||||||
|
this.rsp = rsp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This checks for the defaults available other rules for the keys
|
||||||
|
*/
|
||||||
|
private static Set<String> readSetSmart(String permissionName, Map m, String key) {
|
||||||
|
Set<String> set = readValueAsSet(m, key);
|
||||||
|
if (set == null && well_known_permissions.containsKey(permissionName)) {
|
||||||
|
set = readValueAsSet((Map) well_known_permissions.get(permissionName), key);
|
||||||
|
}
|
||||||
|
if ("method".equals(key)) {
|
||||||
|
if (set != null) {
|
||||||
|
for (String s : set) if (!HTTP_METHODS.contains(s)) return null;
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
return set == null ? Collections.singleton(null) : set;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> edit(Map<String, Object> latestConf, List<CommandOperation> commands) {
|
||||||
|
for (CommandOperation op : commands) {
|
||||||
|
OPERATION operation = null;
|
||||||
|
for (OPERATION o : OPERATION.values()) {
|
||||||
|
if (o.name.equals(op.name)) {
|
||||||
|
operation = o;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (operation == null) {
|
||||||
|
op.unknownOperation();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
latestConf = operation.edit(latestConf, op);
|
||||||
|
if (latestConf == null) return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
return latestConf;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OPERATION {
|
||||||
|
SET_USER_ROLE("set-user-role") {
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> edit(Map<String, Object> latestConf, CommandOperation op) {
|
||||||
|
Map<String, Object> roleMap = getMapValue(latestConf, "user-role");
|
||||||
|
Map<String, Object> map = op.getDataMap();
|
||||||
|
if (op.hasError()) return null;
|
||||||
|
for (Map.Entry<String, Object> e : map.entrySet()) {
|
||||||
|
if (e.getValue() == null) {
|
||||||
|
roleMap.remove(e.getKey());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (e.getValue() instanceof String || e.getValue() instanceof List) {
|
||||||
|
roleMap.put(e.getKey(), e.getValue());
|
||||||
|
} else {
|
||||||
|
op.addError("Unexpected value ");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return latestConf;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
SET_PERMISSION("set-permission") {
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> edit(Map<String, Object> latestConf, CommandOperation op) {
|
||||||
|
String name = op.getStr(NAME);
|
||||||
|
Map<String, Object> dataMap = op.getDataMap();
|
||||||
|
if (op.hasError()) return null;
|
||||||
|
dataMap = getDeepCopy(dataMap, 3);
|
||||||
|
dataMap.remove(NAME);
|
||||||
|
String before = (String) dataMap.remove("before");
|
||||||
|
for (String key : dataMap.keySet()) {
|
||||||
|
if (!Permission.knownKeys.contains(key)) op.addError("Unknown key, " + key);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Permission.load(name, dataMap);
|
||||||
|
} catch (Exception e) {
|
||||||
|
op.addError(e.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Map<String, Object> permissions = getMapValue(latestConf, "permissions");
|
||||||
|
if (before == null) {
|
||||||
|
permissions.put(name, dataMap);
|
||||||
|
} else {
|
||||||
|
Map<String, Object> permissionsCopy = new LinkedHashMap<>();
|
||||||
|
for (Map.Entry<String, Object> e : permissions.entrySet()) {
|
||||||
|
if (e.getKey().equals(before)) permissionsCopy.put(name, dataMap);
|
||||||
|
permissionsCopy.put(e.getKey(), e.getValue());
|
||||||
|
}
|
||||||
|
if (!permissionsCopy.containsKey(name)) {
|
||||||
|
op.addError("Invalid 'before' :" + before);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
latestConf.put("permissions", permissionsCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
return latestConf;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
DELETE_PERMISSION("delete-permission") {
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> edit(Map<String, Object> latestConf, CommandOperation op) {
|
||||||
|
List<String> names = op.getStrs("");
|
||||||
|
if (names == null || names.isEmpty()) {
|
||||||
|
op.addError("Invalid command");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Map<String, Object> p = getMapValue(latestConf, "permissions");
|
||||||
|
for (String s : names) {
|
||||||
|
if (p.remove(s) == null) {
|
||||||
|
op.addError("Unknown permission : " + s);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return latestConf;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public abstract Map<String, Object> edit(Map<String, Object> latestConf, CommandOperation op);
|
||||||
|
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
OPERATION(String s) {
|
||||||
|
this.name = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OPERATION get(String name) {
|
||||||
|
for (OPERATION o : values()) if (o.name.equals(name)) return o;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Set<String> HTTP_METHODS = ImmutableSet.of("GET", "POST", "DELETE", "PUT", "HEAD");
|
||||||
|
|
||||||
|
private static final Map<String, Map<String,Object>> well_known_permissions = (Map) Utils.fromJSONString(
|
||||||
|
" { " +
|
||||||
|
" security-edit :{" +
|
||||||
|
" path:['/admin/authentication','/admin/authorization']," +
|
||||||
|
" method:POST }," +
|
||||||
|
" security-read :{" +
|
||||||
|
" path:['/admin/authentication','/admin/authorization']," +
|
||||||
|
" method:GET }," +
|
||||||
|
" schema-edit :{" +
|
||||||
|
" method:POST," +
|
||||||
|
" path:'/schema/*'}," +
|
||||||
|
" collection-admin-edit :{" +
|
||||||
|
" path:'/admin/collections'}," +
|
||||||
|
" collection-admin-read :{" +
|
||||||
|
" path:'/admin/collections'}," +
|
||||||
|
" schema-read :{" +
|
||||||
|
" method:GET," +
|
||||||
|
" path:'/schema/*'}," +
|
||||||
|
" config-read :{" +
|
||||||
|
" method:GET," +
|
||||||
|
" path:'/config/*'}," +
|
||||||
|
" update :{" +
|
||||||
|
" path:'/update/*'}," +
|
||||||
|
" read :{" +
|
||||||
|
" path:['/update/*', '/get']}," +
|
||||||
|
" config-edit:{" +
|
||||||
|
" method:POST," +
|
||||||
|
" path:'/config/*'}}");
|
||||||
|
|
||||||
|
static {
|
||||||
|
((Map) well_known_permissions.get("collection-admin-edit")).put(Predicate.class.getName(), getPredicate(true));
|
||||||
|
((Map) well_known_permissions.get("collection-admin-read")).put(Predicate.class.getName(), getPredicate(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Predicate<AuthorizationContext> getPredicate(final boolean isEdit) {
|
||||||
|
return new Predicate<AuthorizationContext>() {
|
||||||
|
@Override
|
||||||
|
public boolean test(AuthorizationContext context) {
|
||||||
|
String action = context.getParams().get("action");
|
||||||
|
if (action == null) return false;
|
||||||
|
CollectionParams.CollectionAction collectionAction = CollectionParams.CollectionAction.get(action);
|
||||||
|
if (collectionAction == null) return false;
|
||||||
|
return isEdit ? collectionAction.isWrite : !collectionAction.isWrite;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
System.out.println(Utils.toJSONString(well_known_permissions));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,183 @@
|
||||||
|
package org.apache.solr.security;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.http.auth.BasicUserPrincipal;
|
||||||
|
import org.apache.solr.SolrTestCaseJ4;
|
||||||
|
import org.apache.solr.common.params.MapSolrParams;
|
||||||
|
import org.apache.solr.common.params.SolrParams;
|
||||||
|
import org.apache.solr.common.util.Utils;
|
||||||
|
|
||||||
|
public class TestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
|
||||||
|
|
||||||
|
public void testBasicPermissions() {
|
||||||
|
int STATUS_OK = 200;
|
||||||
|
int FORBIDDEN = 403;
|
||||||
|
int PROMPT_FOR_CREDENTIALS = 401;
|
||||||
|
|
||||||
|
String jsonRules= "{" +
|
||||||
|
" user-role : {" +
|
||||||
|
" steve: [dev,user]," +
|
||||||
|
" tim: [dev,admin]," +
|
||||||
|
" joe: [user]," +
|
||||||
|
" noble:[dev,user]" +
|
||||||
|
" }," +
|
||||||
|
" permissions : {" +
|
||||||
|
" schema-edit :{" +
|
||||||
|
" role:admin" +
|
||||||
|
" }," +
|
||||||
|
" collection-admin-read :{" +
|
||||||
|
" role:null" +
|
||||||
|
" }," +
|
||||||
|
" collection-admin-edit :{" +
|
||||||
|
" role:admin" +
|
||||||
|
" }," +
|
||||||
|
" mycoll_update: {" +
|
||||||
|
" collection:mycoll," +
|
||||||
|
" path:'/update/*'," +
|
||||||
|
" role:[dev,admin]" +
|
||||||
|
" }}}" ;
|
||||||
|
Map initConfig = (Map) Utils.fromJSON(jsonRules.getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
RuleBasedAuthorizationPlugin plugin= new RuleBasedAuthorizationPlugin();
|
||||||
|
plugin.init(initConfig);
|
||||||
|
|
||||||
|
Map<String, Object> values = Utils.makeMap(
|
||||||
|
"resource", "/update/json/docs",
|
||||||
|
"httpMethod", "POST",
|
||||||
|
"collectionRequests", Collections.singletonList(new AuthorizationContext.CollectionRequest("mycoll")),
|
||||||
|
"userPrincipal", new BasicUserPrincipal("tim"));
|
||||||
|
AuthorizationContext context = new MockAuthorizationContext(values);
|
||||||
|
|
||||||
|
AuthorizationResponse authResp = plugin.authorize(context);
|
||||||
|
assertEquals(STATUS_OK, authResp.statusCode);
|
||||||
|
|
||||||
|
values.remove("userPrincipal");
|
||||||
|
authResp = plugin.authorize(context);
|
||||||
|
assertEquals(PROMPT_FOR_CREDENTIALS,authResp.statusCode);
|
||||||
|
|
||||||
|
values.put("userPrincipal", new BasicUserPrincipal("somebody"));
|
||||||
|
authResp = plugin.authorize(context);
|
||||||
|
assertEquals(FORBIDDEN,authResp.statusCode);
|
||||||
|
|
||||||
|
values.put("httpMethod","GET");
|
||||||
|
values.put("resource","/schema");
|
||||||
|
authResp = plugin.authorize(context);
|
||||||
|
assertEquals(STATUS_OK,authResp.statusCode);
|
||||||
|
|
||||||
|
values.put("resource","/schema/fields");
|
||||||
|
authResp = plugin.authorize(context);
|
||||||
|
assertEquals(STATUS_OK,authResp.statusCode);
|
||||||
|
|
||||||
|
values.put("resource","/schema");
|
||||||
|
values.put("httpMethod","POST");
|
||||||
|
authResp = plugin.authorize(context);
|
||||||
|
assertEquals(FORBIDDEN,authResp.statusCode);
|
||||||
|
|
||||||
|
values.put("resource","/admin/collections");
|
||||||
|
values.put("params", new MapSolrParams(Collections.singletonMap("action", "LIST")));
|
||||||
|
values.put("httpMethod","GET");
|
||||||
|
authResp = plugin.authorize(context);
|
||||||
|
assertEquals(STATUS_OK,authResp.statusCode);
|
||||||
|
|
||||||
|
values.remove("userPrincipal");
|
||||||
|
authResp = plugin.authorize(context);
|
||||||
|
assertEquals(STATUS_OK,authResp.statusCode);
|
||||||
|
|
||||||
|
values.put("params", new MapSolrParams(Collections.singletonMap("action", "CREATE")));
|
||||||
|
authResp = plugin.authorize(context);
|
||||||
|
assertEquals(PROMPT_FOR_CREDENTIALS, authResp.statusCode);
|
||||||
|
|
||||||
|
values.put("userPrincipal", new BasicUserPrincipal("somebody"));
|
||||||
|
authResp = plugin.authorize(context);
|
||||||
|
assertEquals(FORBIDDEN,authResp.statusCode);
|
||||||
|
|
||||||
|
values.put("userPrincipal", new BasicUserPrincipal("tim"));
|
||||||
|
authResp = plugin.authorize(context);
|
||||||
|
assertEquals(STATUS_OK,authResp.statusCode);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MockAuthorizationContext extends AuthorizationContext {
|
||||||
|
private final Map<String,Object> values;
|
||||||
|
|
||||||
|
private MockAuthorizationContext(Map<String, Object> values) {
|
||||||
|
this.values = values;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SolrParams getParams() {
|
||||||
|
return (SolrParams) values.get("params");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Principal getUserPrincipal() {
|
||||||
|
return (Principal) values.get("userPrincipal");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHttpHeader(String header) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Enumeration getHeaderNames() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRemoteAddr() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRemoteHost() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<CollectionRequest> getCollectionRequests() {
|
||||||
|
return (List<CollectionRequest>) values.get("collectionRequests");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RequestType getRequestType() {
|
||||||
|
return (RequestType) values.get("requestType");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHttpMethod() {
|
||||||
|
return (String) values.get("httpMethod");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getResource() {
|
||||||
|
return (String) values.get("resource");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue