/* * 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 java.io.IOException; import java.io.StringReader; import java.security.Principal; import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.LinkedList; 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; import org.apache.solr.handler.DumpRequestHandler; import org.apache.solr.handler.ReplicationHandler; import org.apache.solr.handler.SchemaHandler; import org.apache.solr.handler.UpdateRequestHandler; import org.apache.solr.handler.admin.CollectionsHandler; import org.apache.solr.handler.admin.CoreAdminHandler; import org.apache.solr.handler.component.SearchHandler; import org.apache.solr.security.AuthorizationContext.CollectionRequest; import org.apache.solr.security.AuthorizationContext.RequestType; import org.apache.solr.common.util.CommandOperation; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.apache.solr.common.util.Utils.getObjectByPath; import static org.apache.solr.common.util.Utils.makeMap; import static org.apache.solr.common.util.CommandOperation.captureErrors; public class TestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 { String permissions = "{" + " user-role : {" + " steve: [dev,user]," + " tim: [dev,admin]," + " joe: [user]," + " noble:[dev,user]" + " }," + " 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]" + " }," + "{name:read , role:dev }," + "{name:freeforall, path:'/foo', role:'*'}]}"; public void testBasicPermissions() { int STATUS_OK = 200; int FORBIDDEN = 403; int PROMPT_FOR_CREDENTIALS = 401; checkRules(makeMap("resource", "/update/json/docs", "httpMethod", "POST", "userPrincipal", "unknownuser", "collectionRequests", "freeforall", "handler", new UpdateRequestHandler()) , STATUS_OK); checkRules(makeMap("resource", "/update/json/docs", "httpMethod", "POST", "userPrincipal", "tim", "collectionRequests", "mycoll", "handler", new UpdateRequestHandler()) , STATUS_OK); checkRules(makeMap("resource", "/update/json/docs", "httpMethod", "POST", "collectionRequests", "mycoll", "handler", new UpdateRequestHandler()) , PROMPT_FOR_CREDENTIALS); checkRules(makeMap("resource", "/schema", "userPrincipal", "somebody", "collectionRequests", "mycoll", "httpMethod", "POST", "handler", new SchemaHandler()) , FORBIDDEN); checkRules(makeMap("resource", "/schema", "userPrincipal", "somebody", "collectionRequests", "mycoll", "httpMethod", "GET", "handler", new SchemaHandler() ) , STATUS_OK); checkRules(makeMap("resource", "/schema/fields", "userPrincipal", "somebody", "collectionRequests", "mycoll", "httpMethod", "GET", "handler", new SchemaHandler()) , STATUS_OK); checkRules(makeMap("resource", "/schema", "userPrincipal", "somebody", "collectionRequests", "mycoll", "httpMethod", "POST", "handler", new SchemaHandler()) , FORBIDDEN); checkRules(makeMap("resource", "/admin/collections", "userPrincipal", "tim", "requestType", RequestType.ADMIN, "collectionRequests", null, "httpMethod", "GET", "handler", new CollectionsHandler(), "params", new MapSolrParams(singletonMap("action", "LIST"))) , STATUS_OK); checkRules(makeMap("resource", "/admin/collections", "userPrincipal", null, "requestType", RequestType.ADMIN, "collectionRequests", null, "httpMethod", "GET", "handler", new CollectionsHandler(), "params", new MapSolrParams(singletonMap("action", "LIST"))) , STATUS_OK); checkRules(makeMap("resource", "/admin/collections", "userPrincipal", null, "requestType", RequestType.ADMIN, "collectionRequests", null, "handler", new CollectionsHandler(), "params", new MapSolrParams(singletonMap("action", "CREATE"))) , PROMPT_FOR_CREDENTIALS); checkRules(makeMap("resource", "/admin/collections", "userPrincipal", null, "requestType", RequestType.ADMIN, "collectionRequests", null, "handler", new CollectionsHandler(), "params", new MapSolrParams(singletonMap("action", "RELOAD"))) , PROMPT_FOR_CREDENTIALS); checkRules(makeMap("resource", "/admin/collections", "userPrincipal", "somebody", "requestType", RequestType.ADMIN, "collectionRequests", null, "handler", new CollectionsHandler(), "params", new MapSolrParams(singletonMap("action", "CREATE"))) , FORBIDDEN); checkRules(makeMap("resource", "/admin/collections", "userPrincipal", "tim", "requestType", RequestType.ADMIN, "collectionRequests", null, "handler", new CollectionsHandler(), "params", new MapSolrParams(singletonMap("action", "CREATE"))) , STATUS_OK); checkRules(makeMap("resource", "/select", "httpMethod", "GET", "handler", new SearchHandler(), "collectionRequests", singletonList(new CollectionRequest("mycoll")), "userPrincipal", "joe") , FORBIDDEN); Map rules = (Map) Utils.fromJSONString(permissions); ((Map)rules.get("user-role")).put("cio","su"); ((List)rules.get("permissions")).add( makeMap("name", "all", "role", "su")); checkRules(makeMap("resource", ReplicationHandler.PATH, "httpMethod", "POST", "userPrincipal", "tim", "handler", new ReplicationHandler(), "collectionRequests", singletonList(new CollectionRequest("mycoll")) ) , FORBIDDEN, rules); checkRules(makeMap("resource", ReplicationHandler.PATH, "httpMethod", "POST", "userPrincipal", "cio", "handler", new ReplicationHandler(), "collectionRequests", singletonList(new CollectionRequest("mycoll")) ) , STATUS_OK, rules); checkRules(makeMap("resource", "/admin/collections", "userPrincipal", "tim", "requestType", AuthorizationContext.RequestType.ADMIN, "collectionRequests", null, "handler", new CollectionsHandler(), "params", new MapSolrParams(singletonMap("action", "CREATE"))) , STATUS_OK, rules); rules = (Map) Utils.fromJSONString(permissions); ((List)rules.get("permissions")).add( makeMap("name", "core-admin-edit", "role", "su")); ((List)rules.get("permissions")).add( makeMap("name", "core-admin-read", "role", "user")); ((Map)rules.get("user-role")).put("cio","su"); ((List)rules.get("permissions")).add( makeMap("name", "all", "role", "su")); permissions = Utils.toJSONString(rules); checkRules(makeMap("resource", "/admin/cores", "userPrincipal", null, "requestType", RequestType.ADMIN, "collectionRequests", null, "handler", new CoreAdminHandler(null), "params", new MapSolrParams(singletonMap("action", "CREATE"))) , PROMPT_FOR_CREDENTIALS); checkRules(makeMap("resource", "/admin/cores", "userPrincipal", "joe", "requestType", RequestType.ADMIN, "collectionRequests", null, "handler", new CoreAdminHandler(null), "params", new MapSolrParams(singletonMap("action", "CREATE"))) , FORBIDDEN); checkRules(makeMap("resource", "/admin/cores", "userPrincipal", "joe", "requestType", RequestType.ADMIN, "collectionRequests", null, "handler", new CoreAdminHandler(null), "params", new MapSolrParams(singletonMap("action", "STATUS"))) , STATUS_OK); checkRules(makeMap("resource", "/admin/cores", "userPrincipal", "cio", "requestType", RequestType.ADMIN, "collectionRequests", null, "handler", new CoreAdminHandler(null), "params", new MapSolrParams(singletonMap("action", "CREATE"))) ,STATUS_OK ); rules = (Map) Utils.fromJSONString(permissions); List permissions = (List) rules.get("permissions"); permissions.remove(permissions.size() -1);//remove the 'all' permission permissions.add(makeMap("name", "test-params", "role", "admin", "path", "/x", "params", makeMap("key", Arrays.asList("REGEX:(?i)val1", "VAL2")))); this.permissions = Utils.toJSONString(rules); checkRules(makeMap("resource", "/x", "userPrincipal", null, "requestType", RequestType.UNKNOWN, "collectionRequests", "go", "handler", new DumpRequestHandler(), "params", new MapSolrParams(singletonMap("key", "VAL1"))) , PROMPT_FOR_CREDENTIALS); checkRules(makeMap("resource", "/x", "userPrincipal", null, "requestType", RequestType.UNKNOWN, "collectionRequests", "go", "handler", new DumpRequestHandler(), "params", new MapSolrParams(singletonMap("key", "Val1"))) , PROMPT_FOR_CREDENTIALS); checkRules(makeMap("resource", "/x", "userPrincipal", null, "requestType", RequestType.UNKNOWN, "collectionRequests", "go", "handler", new DumpRequestHandler(), "params", new MapSolrParams(singletonMap("key", "Val1"))) , PROMPT_FOR_CREDENTIALS); checkRules(makeMap("resource", "/x", "userPrincipal", "joe", "requestType", RequestType.UNKNOWN, "collectionRequests", "go", "handler", new DumpRequestHandler(), "params", new MapSolrParams(singletonMap("key", "Val1"))) , FORBIDDEN); checkRules(makeMap("resource", "/x", "userPrincipal", "joe", "requestType", RequestType.UNKNOWN, "collectionRequests", "go", "handler", new DumpRequestHandler(), "params", new MapSolrParams(singletonMap("key", "Val2"))) , STATUS_OK); checkRules(makeMap("resource", "/x", "userPrincipal", "joe", "requestType", RequestType.UNKNOWN, "collectionRequests", "go", "handler", new DumpRequestHandler(), "params", new MapSolrParams(singletonMap("key", "VAL2"))) , FORBIDDEN); checkRules(makeMap("resource", "/update", "userPrincipal", "solr", "requestType", RequestType.UNKNOWN, "collectionRequests", "go", "handler", new UpdateRequestHandler(), "params", new MapSolrParams(singletonMap("key", "VAL2"))) , FORBIDDEN, (Map<String, Object>) Utils.fromJSONString( "{user-role:{" + " admin:[admin_role]," + " update:[update_role]," + " solr:[read_role]}," + " permissions:[" + " {name:update, role:[admin_role,update_role]}," + " {name:read, role:[admin_role,update_role,read_role]}" + "]}")); } public void testEditRules() throws IOException { Perms perms = new Perms(); perms.runCmd("{set-permission : {name: config-edit, role: admin } }", true); assertEquals("config-edit", getObjectByPath(perms.conf, false, "permissions[0]/name")); assertEquals(1 , perms.getVal("permissions[0]/index")); assertEquals("admin", perms.getVal("permissions[0]/role")); perms.runCmd("{set-permission : {name: config-edit, role: [admin, dev], index:2 } }", false); perms.runCmd("{set-permission : {name: config-edit, role: [admin, dev], index:1}}", true); Collection roles = (Collection) perms.getVal("permissions[0]/role"); assertEquals(2, roles.size()); assertTrue(roles.contains("admin")); assertTrue(roles.contains("dev")); perms.runCmd("{set-permission : {role: [admin, dev], collection: x , path: '/a/b' , method :[GET, POST] }}", true); assertNotNull(perms.getVal("permissions[1]")); assertEquals("x", perms.getVal("permissions[1]/collection")); assertEquals("/a/b", perms.getVal("permissions[1]/path")); perms.runCmd("{update-permission : {index : 2, method : POST }}", true); assertEquals("POST", perms.getVal("permissions[1]/method")); perms.runCmd("{set-permission : {name : read, collection : y, role:[guest, dev] , before :2}}", true); assertNotNull(perms.getVal("permissions[2]")); assertEquals("y", perms.getVal("permissions[1]/collection")); assertEquals("read", perms.getVal("permissions[1]/name")); perms.runCmd("{delete-permission : 3}", true); assertTrue(captureErrors(perms.parsedCommands).isEmpty()); assertEquals("y", perms.getVal("permissions[1]/collection")); } static class Perms { Map conf = new HashMap<>(); ConfigEditablePlugin plugin = new RuleBasedAuthorizationPlugin(); List<CommandOperation> parsedCommands; public void runCmd(String cmds, boolean failOnError) throws IOException { parsedCommands = CommandOperation.parse(new StringReader(cmds)); LinkedList ll = new LinkedList(); Map<String, Object> edited = plugin.edit(conf, parsedCommands); if(edited!= null) conf = edited; List<Map> maps = captureErrors(parsedCommands); if(failOnError){ assertTrue("unexpected error ,"+maps , maps.isEmpty()); } else { assertFalse("expected error", maps.isEmpty()); } } public Object getVal(String path){ return getObjectByPath(conf,false, path); } } private void checkRules(Map<String, Object> values, int expected) { checkRules(values,expected,(Map) Utils.fromJSONString(permissions)); } private void checkRules(Map<String, Object> values, int expected, Map<String ,Object> permissions) { AuthorizationContext context = new MockAuthorizationContext(values); RuleBasedAuthorizationPlugin plugin = new RuleBasedAuthorizationPlugin(); plugin.init(permissions); AuthorizationResponse authResp = plugin.authorize(context); assertEquals(expected, 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() { SolrParams params = (SolrParams) values.get("params"); return params == null ? new MapSolrParams(new HashMap<String, String>()) : params; } @Override public Principal getUserPrincipal() { Object userPrincipal = values.get("userPrincipal"); return userPrincipal == null ? null : new BasicUserPrincipal(String.valueOf(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() { Object collectionRequests = values.get("collectionRequests"); if (collectionRequests instanceof String) { return singletonList(new CollectionRequest((String)collectionRequests)); } return (List<CollectionRequest>) collectionRequests; } @Override public RequestType getRequestType() { return (RequestType) values.get("requestType"); } @Override public String getHttpMethod() { return (String) values.get("httpMethod"); } @Override public Object getHandler() { Object handler = values.get("handler"); return handler instanceof String ? (PermissionNameProvider) request -> PermissionNameProvider.Name.get((String) handler) : handler; } @Override public String getResource() { return (String) values.get("resource"); } } static String testPerms = "{user-role:{" + " admin:[admin_role]," + " update:[update_role]," + " solr:[read_role]}," + " permissions:[" + " {name:update,role:[admin_role,update_role]}," + " {name:read,role:[admin_role,update_role,read_role]" + "]}"; }