SOLR-7838: changed the permissions froma map to an array so that order is obvious

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1695308 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Noble Paul 2015-08-11 14:28:50 +00:00
parent 040849908c
commit 11ac3f7f88
8 changed files with 212 additions and 67 deletions

View File

@ -18,6 +18,7 @@ package org.apache.solr.handler.admin;
*/
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
@ -141,9 +142,14 @@ public class SecurityConfHandler extends RequestHandlerBase {
}
public static Map<String, Object> getMapValue(Map<String, Object> lookupMap, String key) {
Map<String, Object> roleMap = (Map<String, Object>) lookupMap.get(key);
if (roleMap == null) lookupMap.put(key, roleMap = new LinkedHashMap<>());
return roleMap;
Map<String, Object> m = (Map<String, Object>) lookupMap.get(key);
if (m == null) lookupMap.put(key, m = new LinkedHashMap<>());
return m;
}
public static List getListValue(Map<String, Object> lookupMap, String key) {
List l = (List) lookupMap.get(key);
if (l == null) lookupMap.put(key, l= new ArrayList());
return l;
}
@Override

View File

@ -39,6 +39,8 @@ import org.apache.solr.util.CommandOperation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.util.Collections.singleton;
import static org.apache.solr.handler.admin.SecurityConfHandler.getListValue;
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;
@ -86,12 +88,15 @@ public class RuleBasedAuthorizationPlugin implements AuthorizationPlugin, Config
@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;
}
if (collectionRequests.isEmpty()) {
MatchStatus flag = checkCollPerm(mapping.get(""), context);
return flag.rsp;
}
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);
@ -160,12 +165,11 @@ public class RuleBasedAuthorizationPlugin implements AuthorizationPlugin, Config
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;
List<Map> perms = getListValue(initInfo, "permissions");
for (Map o : perms) {
Permission p;
try {
p = Permission.load((String) e.getKey(), (Map) e.getValue());
p = Permission.load(o);
} catch (Exception exp) {
log.error("Invalid permission ", exp);
continue;
@ -175,8 +179,8 @@ public class RuleBasedAuthorizationPlugin implements AuthorizationPlugin, Config
}
}
//this is to do optimized lookup of permissions for a given collection/path
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());
@ -186,7 +190,6 @@ public class RuleBasedAuthorizationPlugin implements AuthorizationPlugin, Config
perms.add(permission);
}
}
}
/**
@ -199,7 +202,14 @@ public class RuleBasedAuthorizationPlugin implements AuthorizationPlugin, Config
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 == null) {
if("collection".equals(key)){
//for collection collection: null means a core admin/ collection admin request
// otherwise it means a request where collection name is ignored
return m.containsKey(key) ? singleton("") : singleton(null);
}
return null;
}
if (val instanceof Collection) {
Collection list = (Collection) val;
for (Object o : list) result.add(String.valueOf(o));
@ -223,13 +233,15 @@ public class RuleBasedAuthorizationPlugin implements AuthorizationPlugin, Config
private Permission() {
}
static Permission load(String name, Map m) {
static Permission load(Map m) {
Permission p = new Permission();
String name = (String) m.get(NAME);
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");
disAllowed.remove("role");//these are the only
disAllowed.remove(NAME);//allowed keys for well-known permissions
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);
@ -245,7 +257,7 @@ public class RuleBasedAuthorizationPlugin implements AuthorizationPlugin, Config
return p;
}
static final Set<String> knownKeys = ImmutableSet.of("collection", "role", "params", "path", "method");
static final Set<String> knownKeys = ImmutableSet.of("collection", "role", "params", "path", "method", NAME);
}
enum MatchStatus {
@ -275,7 +287,7 @@ public class RuleBasedAuthorizationPlugin implements AuthorizationPlugin, Config
}
return set;
}
return set == null ? Collections.singleton(null) : set;
return set == null ? singleton(null) : set;
}
@Override
@ -328,36 +340,53 @@ public class RuleBasedAuthorizationPlugin implements AuthorizationPlugin, Config
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);
Permission.load(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());
List<Map> permissions = getListValue(latestConf, "permissions");
List<Map> permissionsCopy = new ArrayList<>();
boolean added = false;
for (Map e : permissions) {
Object n = e.get(NAME);
if (n.equals(before) || n.equals(name)) {
added = true;
permissionsCopy.add(dataMap);
}
if (!permissionsCopy.containsKey(name)) {
op.addError("Invalid 'before' :" + before);
return null;
}
latestConf.put("permissions", permissionsCopy);
if (!n.equals(name)) permissionsCopy.add(e);
}
if (!added && before != null) {
op.addError("Invalid 'before' :" + before);
return null;
}
if (!added) permissionsCopy.add(dataMap);
latestConf.put("permissions", permissionsCopy);
return latestConf;
}
},
UPDATE_PERMISSION("update-permission") {
@Override
public Map<String, Object> edit(Map<String, Object> latestConf, CommandOperation op) {
String name = op.getStr(NAME);
if (op.hasError()) return null;
for (Map permission : (List<Map>) getListValue(latestConf, "permissions")) {
if (name.equals(permission.get(NAME))) {
LinkedHashMap copy = new LinkedHashMap<>(permission);
copy.putAll(op.getDataMap());
op.setCommandData(copy);
return SET_PERMISSION.edit(latestConf, op);
}
}
op.addError("No such permission " + name);
return null;
}
},
DELETE_PERMISSION("delete-permission") {
@Override
public Map<String, Object> edit(Map<String, Object> latestConf, CommandOperation op) {
@ -366,13 +395,23 @@ public class RuleBasedAuthorizationPlugin implements AuthorizationPlugin, Config
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;
names = new ArrayList<>(names);
List<Map> copy = new ArrayList<>();
List<Map> p = getListValue(latestConf, "permissions");
for (Map map : p) {
Object n = map.get(NAME);
if (names.contains(n)) {
names.remove(n);
continue;
} else {
copy.add(map);
}
}
if (!names.isEmpty()) {
op.addError("Unknown permission name(s) " + names);
return null;
}
latestConf.put("permissions", copy);
return latestConf;
}
};
@ -397,16 +436,20 @@ public class RuleBasedAuthorizationPlugin implements AuthorizationPlugin, Config
" { " +
" security-edit :{" +
" path:['/admin/authentication','/admin/authorization']," +
" collection:null," +
" method:POST }," +
" security-read :{" +
" path:['/admin/authentication','/admin/authorization']," +
" method:GET }," +
" collection:null," +
" method:GET}," +
" schema-edit :{" +
" method:POST," +
" path:'/schema/*'}," +
" collection-admin-edit :{" +
" collection:null," +
" path:'/admin/collections'}," +
" collection-admin-read :{" +
" collection:null," +
" path:'/admin/collections'}," +
" schema-read :{" +
" method:GET," +

View File

@ -59,6 +59,9 @@ public class CommandOperation {
Object o = getMapVal(key);
return o == null ? def : String.valueOf(o);
}
public void setCommandData(Object o){
commandData = o;
}
public Map<String,Object> getDataMap() {
if (commandData instanceof Map) {

View File

@ -97,13 +97,56 @@ public class SecurityConfHandlerTest extends SolrTestCaseJ4 {
List tomRoles = (List) userRoles.get("tom");
assertTrue(tomRoles.contains("admin"));
assertTrue(tomRoles.contains("dev"));
Map permissions = (Map) authzconf.get("permissions");
List<Map> permissions = (List<Map>) authzconf.get("permissions");
assertEquals(2, permissions.size());
for (Object p : permissions.entrySet()) {
Map.Entry e = (Map.Entry) p;
assertEquals("some-permission", e.getKey());
for (Map p : permissions) {
assertEquals("some-permission", p.get("name"));
break;
}
command = "{\n" +
"'set-permission':{'name': 'security-edit',\n" +
" 'role': ['admin','dev']\n" +
" }}";
req = new LocalSolrQueryRequest(null, new ModifiableSolrParams());
req.getContext().put("httpMethod","POST");
req.getContext().put("path","/admin/authorization");
o = new ContentStreamBase.ByteArrayStream(command.getBytes(StandardCharsets.UTF_8),"");
req.setContentStreams(Collections.singletonList(o));
rsp = new SolrQueryResponse();
handler.handleRequestBody(req, rsp);
authzconf = (Map) ((ConfigData) handler.m.get("/security.json")).data.get("authorization");
permissions = (List<Map>) authzconf.get("permissions");
Map p = permissions.get(1);
assertEquals("security-edit", p.get("name"));
List rol = (List) p.get("role");
assertEquals( "admin", rol.get(0));
assertEquals( "dev", rol.get(1));
command = "{\n" +
"'update-permission':{'name': 'some-permission',\n" +
" 'role': ['guest','admin']\n" +
" }}";
req = new LocalSolrQueryRequest(null, new ModifiableSolrParams());
req.getContext().put("httpMethod","POST");
req.getContext().put("path","/admin/authorization");
o = new ContentStreamBase.ByteArrayStream(command.getBytes(StandardCharsets.UTF_8),"");
req.setContentStreams(Collections.singletonList(o));
rsp = new SolrQueryResponse();
handler.handleRequestBody(req, rsp);
authzconf = (Map) ((ConfigData) handler.m.get("/security.json")).data.get("authorization");
permissions = (List<Map>) authzconf.get("permissions");
p = permissions.get(0);
assertEquals("some-permission", p.get("name"));
rol = (List) p.get("role");
assertEquals( "guest", rol.get(0));
assertEquals( "admin", rol.get(1));
command = "{\n" +
"'delete-permission': 'some-permission',\n" +
"'set-user-role':{'tom':null}\n" +
@ -119,12 +162,15 @@ public class SecurityConfHandlerTest extends SolrTestCaseJ4 {
authzconf = (Map) ((ConfigData) handler.m.get("/security.json")).data.get("authorization");
userRoles = (Map) authzconf.get("user-role");
assertEquals(0, userRoles.size());
permissions = (Map) authzconf.get("permissions");
permissions = (List<Map>) authzconf.get("permissions");
assertEquals(1, permissions.size());
assertNull(permissions.get("some-permission"));
for (Map permission : permissions) {
assertFalse("some-permission".equals(permission.get("name")));
}
command = "{\n" +
"'set-permission':{'name': 'security-edit',\n" +
" 'method':'POST',"+ // security edit is a well-known permission , only role attribute should be provided
" 'method':'POST',"+ // -ve test security edit is a well-known permission , only role attribute should be provided
" 'role': 'admin'\n" +
" }}";
req = new LocalSolrQueryRequest(null, new ModifiableSolrParams());
@ -136,7 +182,6 @@ public class SecurityConfHandlerTest extends SolrTestCaseJ4 {
handler.handleRequestBody(req, rsp);
List l = (List) ((Map) ((List)rsp.getValues().get("errorMessages")).get(0)).get("errorMessages");
assertEquals(1, l.size());
}

View File

@ -131,7 +131,7 @@ public class BasicAuthIntegrationTest extends TestMiniSolrCloudCluster {
r = cl.execute(httpPost);
assertEquals(200, r.getStatusLine().getStatusCode());
verifySecurityStatus(cl, baseUrl+"/admin/authorization", "authorization/permissions/x-update/collection", "x", 20);
verifySecurityStatus(cl, baseUrl+"/admin/authorization", "authorization/permissions[1]/collection", "x", 20);
}
@ -206,5 +206,5 @@ public class BasicAuthIntegrationTest extends TestMiniSolrCloudCluster {
" 'authorization':{\n" +
" 'class':'solr.RuleBasedAuthorizationPlugin',\n" +
" 'user-role':{'solr':'admin'},\n" +
" 'permissions':{'security-edit':{'role':'admin'}}}}";
" 'permissions':[{'name':'security-edit','role':'admin'}]}}";
}

View File

@ -19,6 +19,7 @@ package org.apache.solr.security;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
@ -44,21 +45,18 @@ public class TestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
" joe: [user]," +
" noble:[dev,user]" +
" }," +
" permissions : {" +
" schema-edit :{" +
" role:admin" +
" }," +
" collection-admin-read :{" +
" role:null" +
" }," +
" collection-admin-edit :{" +
" role:admin" +
" }," +
" mycoll_update: {" +
" permissions : [" +
" {name:'schema-edit'," +
" role:admin}," +
" {name:'collection-admin-read'," +
" role:null}," +
" {name:collection-admin-edit ," +
" role:admin}," +
" {name:mycoll_update," +
" collection:mycoll," +
" path:'/update/*'," +
" role:[dev,admin]" +
" }}}" ;
" }]}" ;
Map initConfig = (Map) Utils.fromJSON(jsonRules.getBytes(StandardCharsets.UTF_8));
RuleBasedAuthorizationPlugin plugin= new RuleBasedAuthorizationPlugin();
@ -97,6 +95,7 @@ public class TestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 {
assertEquals(FORBIDDEN,authResp.statusCode);
values.put("resource","/admin/collections");
values.put("collectionRequests",new ArrayList<>());
values.put("params", new MapSolrParams(Collections.singletonMap("action", "LIST")));
values.put("httpMethod","GET");
authResp = plugin.authorize(context);

View File

@ -26,6 +26,7 @@ import java.util.Map;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.common.util.Utils;
import org.junit.Assert;
/**
@ -154,4 +155,26 @@ public class TestUtils extends SolrTestCaseJ4 {
assertEquals( num, NumberUtils.SortableStr2long(sortable, 0, sortable.length() ) );
assertEquals( Long.toString(num), NumberUtils.SortableStr2long(sortable) );
}
public void testUtilsJSPath(){
String json = "{\n" +
" 'authorization':{\n" +
" 'class':'solr.RuleBasedAuthorizationPlugin',\n" +
" 'user-role':{\n" +
" 'solr':'admin',\n" +
" 'harry':'admin'},\n" +
" 'permissions':[{\n" +
" 'name':'security-edit',\n" +
" 'role':'admin'},\n" +
" {\n" +
" 'name':'x-update',\n" +
" 'collection':'x',\n" +
" 'path':'/update/*',\n" +
" 'role':'dev'}],\n" +
" '':{'v':4}}}";
Map m = (Map) Utils.fromJSONString(json);
assertEquals("x-update", Utils.getObjectByPath(m,false, "authorization/permissions[1]/name"));
}
}

View File

@ -30,6 +30,8 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.solr.common.SolrException;
import org.noggit.CharArr;
@ -121,16 +123,37 @@ public class Utils {
}
}
public static Object getObjectByPath(Map root, boolean onlyPrimitive, String hierarchy) {
return getObjectByPath(root, onlyPrimitive, StrUtils.splitSmart(hierarchy, '/'));
}
public static Object getObjectByPath(Map root, boolean onlyPrimitive, List<String> hierarchy) {
Map obj = root;
for (int i = 0; i < hierarchy.size(); i++) {
int idx = -1;
String s = hierarchy.get(i);
if (s.endsWith("]")) {
Matcher matcher = ARRAY_ELEMENT_INDEX.matcher(s);
if (matcher.find()) {
s = matcher.group(1);
idx = Integer.parseInt(matcher.group(2));
}
}
if (i < hierarchy.size() - 1) {
if (!(obj.get(s) instanceof Map)) return null;
obj = (Map) obj.get(s);
if (obj == null) return null;
Object o = obj.get(s);
if (o == null) return null;
if (idx > -1) {
List l = (List) o;
o = idx < l.size() ? l.get(idx) : null;
}
if (!(o instanceof Map)) return null;
obj = (Map) o;
} else {
Object val = obj.get(s);
if (idx > -1) {
List l = (List) val;
val = idx < l.size() ? l.get(idx) : null;
}
if (onlyPrimitive && val instanceof Map) {
return null;
}
@ -140,4 +163,7 @@ public class Utils {
return false;
}
public static final Pattern ARRAY_ELEMENT_INDEX = Pattern
.compile("(\\S*?)\\[(\\d+)\\]");
}