| /* |
| * 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 java.util.stream.Collectors; |
| |
| 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.CommandOperation; |
| 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.admin.PropertiesRequestHandler; |
| import org.apache.solr.handler.component.SearchHandler; |
| import org.apache.solr.request.SolrRequestHandler; |
| import org.apache.solr.security.AuthorizationContext.CollectionRequest; |
| import org.apache.solr.security.AuthorizationContext.RequestType; |
| import org.hamcrest.core.IsInstanceOf; |
| import org.hamcrest.core.IsNot; |
| import org.junit.Test; |
| |
| import static java.util.Collections.emptyMap; |
| import static java.util.Collections.singletonList; |
| import static java.util.Collections.singletonMap; |
| import static org.apache.solr.common.util.CommandOperation.captureErrors; |
| import static org.apache.solr.common.util.Utils.getObjectByPath; |
| import static org.apache.solr.common.util.Utils.makeMap; |
| |
| /** |
| * Base class for testing RBAC. This will test the {@link RuleBasedAuthorizationPlugin} implementation |
| * but also serves as a base class for testing other sub classes |
| */ |
| @SuppressWarnings("unchecked") |
| public class BaseTestRuleBasedAuthorizationPlugin extends SolrTestCaseJ4 { |
| @SuppressWarnings({"rawtypes"}) |
| protected Map rules; |
| |
| final int STATUS_OK = 200; |
| final int FORBIDDEN = 403; |
| final int PROMPT_FOR_CREDENTIALS = 401; |
| |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| resetPermissionsAndRoles(); |
| } |
| |
| protected void resetPermissionsAndRoles() { |
| 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:'*'}]}"; |
| rules = (Map) Utils.fromJSONString(permissions); |
| } |
| |
| @Test |
| public void testBasicPermissions() { |
| 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); |
| |
| setUserRole("cio", "su"); |
| addPermission("all", "su"); |
| |
| checkRules(makeMap("resource", ReplicationHandler.PATH, |
| "httpMethod", "POST", |
| "userPrincipal", "tim", |
| "handler", new ReplicationHandler(), |
| "collectionRequests", singletonList(new CollectionRequest("mycoll")) ) |
| , FORBIDDEN); |
| |
| checkRules(makeMap("resource", ReplicationHandler.PATH, |
| "httpMethod", "POST", |
| "userPrincipal", "cio", |
| "handler", new ReplicationHandler(), |
| "collectionRequests", singletonList(new CollectionRequest("mycoll")) ) |
| , STATUS_OK); |
| |
| checkRules(makeMap("resource", "/admin/collections", |
| "userPrincipal", "tim", |
| "requestType", AuthorizationContext.RequestType.ADMIN, |
| "collectionRequests", null, |
| "handler", new CollectionsHandler(), |
| "params", new MapSolrParams(singletonMap("action", "CREATE"))) |
| , STATUS_OK); |
| |
| resetPermissionsAndRoles(); |
| addPermission("core-admin-edit", "su"); |
| addPermission("core-admin-read", "user"); |
| setUserRole("cio", "su"); |
| addPermission("all", "su"); |
| |
| 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); |
| |
| resetPermissionsAndRoles(); |
| addPermission("test-params", "admin", "/x", makeMap("key", Arrays.asList("REGEX:(?i)val1", "VAL2"))); |
| |
| 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); |
| |
| Map<String, Object> customRules = (Map<String, Object>) Utils.fromJSONString( |
| "{permissions:[" + |
| " {name:update, role:[admin_role,update_role]}," + |
| " {name:read, role:[admin_role,update_role,read_role]}" + |
| "]}"); |
| |
| clearUserRoles(); |
| setUserRole("admin", "admin_role"); |
| setUserRole("update", "update_role"); |
| setUserRole("solr", "read_role"); |
| |
| checkRules(makeMap("resource", "/update", |
| "userPrincipal", "solr", |
| "requestType", RequestType.UNKNOWN, |
| "collectionRequests", "go", |
| "handler", new UpdateRequestHandler(), |
| "params", new MapSolrParams(singletonMap("key", "VAL2"))) |
| , FORBIDDEN, customRules); |
| } |
| |
| /* |
| * RuleBasedAuthorizationPlugin handles requests differently based on whether the underlying handler implements |
| * PermissionNameProvider or not. If this test fails because UpdateRequestHandler stops implementing |
| * PermissionNameProvider, or PropertiesRequestHandler starts to, then just change the handlers used here. |
| */ |
| @Test |
| public void testAllPermissionAllowsActionsWhenUserHasCorrectRole() { |
| SolrRequestHandler handler = new UpdateRequestHandler(); |
| assertThat(handler, new IsInstanceOf(PermissionNameProvider.class)); |
| setUserRole("dev", "dev"); |
| setUserRole("admin", "admin"); |
| addPermission("all", "dev", "admin"); |
| checkRules(makeMap("resource", "/update", |
| "userPrincipal", "dev", |
| "requestType", RequestType.UNKNOWN, |
| "collectionRequests", "go", |
| "handler", handler, |
| "params", new MapSolrParams(singletonMap("key", "VAL2"))) |
| , STATUS_OK); |
| |
| handler = new PropertiesRequestHandler(); |
| assertThat(handler, new IsNot<>(new IsInstanceOf(PermissionNameProvider.class))); |
| checkRules(makeMap("resource", "/admin/info/properties", |
| "userPrincipal", "dev", |
| "requestType", RequestType.UNKNOWN, |
| "collectionRequests", "go", |
| "handler", handler, |
| "params", new MapSolrParams(emptyMap())) |
| , STATUS_OK); |
| } |
| |
| |
| /* |
| * RuleBasedAuthorizationPlugin handles requests differently based on whether the underlying handler implements |
| * PermissionNameProvider or not. If this test fails because UpdateRequestHandler stops implementing |
| * PermissionNameProvider, or PropertiesRequestHandler starts to, then just change the handlers used here. |
| */ |
| @Test |
| public void testAllPermissionAllowsActionsWhenAssociatedRoleIsWildcard() { |
| SolrRequestHandler handler = new UpdateRequestHandler(); |
| assertThat(handler, new IsInstanceOf(PermissionNameProvider.class)); |
| setUserRole("dev", "dev"); |
| setUserRole("admin", "admin"); |
| addPermission("all", "*"); |
| checkRules(makeMap("resource", "/update", |
| "userPrincipal", "dev", |
| "requestType", RequestType.UNKNOWN, |
| "collectionRequests", "go", |
| "handler", new UpdateRequestHandler(), |
| "params", new MapSolrParams(singletonMap("key", "VAL2"))) |
| , STATUS_OK); |
| |
| handler = new PropertiesRequestHandler(); |
| assertThat(handler, new IsNot<>(new IsInstanceOf(PermissionNameProvider.class))); |
| checkRules(makeMap("resource", "/admin/info/properties", |
| "userPrincipal", "dev", |
| "requestType", RequestType.UNKNOWN, |
| "collectionRequests", "go", |
| "handler", handler, |
| "params", new MapSolrParams(emptyMap())) |
| , STATUS_OK); |
| } |
| |
| /* |
| * RuleBasedAuthorizationPlugin handles requests differently based on whether the underlying handler implements |
| * PermissionNameProvider or not. If this test fails because UpdateRequestHandler stops implementing |
| * PermissionNameProvider, or PropertiesRequestHandler starts to, then just change the handlers used here. |
| */ |
| @Test |
| public void testAllPermissionDeniesActionsWhenUserIsNotCorrectRole() { |
| SolrRequestHandler handler = new UpdateRequestHandler(); |
| assertThat(handler, new IsInstanceOf(PermissionNameProvider.class)); |
| setUserRole("dev", "dev"); |
| setUserRole("admin", "admin"); |
| addPermission("all", "admin"); |
| checkRules(makeMap("resource", "/update", |
| "userPrincipal", "dev", |
| "requestType", RequestType.UNKNOWN, |
| "collectionRequests", "go", |
| "handler", new UpdateRequestHandler(), |
| "params", new MapSolrParams(singletonMap("key", "VAL2"))) |
| , FORBIDDEN); |
| |
| handler = new PropertiesRequestHandler(); |
| assertThat(handler, new IsNot<>(new IsInstanceOf(PermissionNameProvider.class))); |
| checkRules(makeMap("resource", "/admin/info/properties", |
| "userPrincipal", "dev", |
| "requestType", RequestType.UNKNOWN, |
| "collectionRequests", "go", |
| "handler", handler, |
| "params", new MapSolrParams(emptyMap())) |
| , FORBIDDEN); |
| } |
| |
| void addPermission(String permissionName, String role, String path, Map<String, Object> params) { |
| ((List)rules.get("permissions")).add( makeMap("name", permissionName, "role", role, "path", path, "params", params)); |
| } |
| |
| void removePermission(String name) { |
| List<Map<String,Object>> oldPerm = ((List) rules.get("permissions")); |
| List<Map<String, Object>> newPerm = oldPerm.stream().filter(p -> !p.get("name").equals(name)).collect(Collectors.toList()); |
| rules.put("permissions", newPerm); |
| } |
| |
| protected void addPermission(String permissionName, String... roles) { |
| ((List)rules.get("permissions")).add( makeMap("name", permissionName, "role", Arrays.asList(roles))); |
| } |
| |
| void clearUserRoles() { |
| rules.put("user-role", new HashMap<String,Object>()); |
| } |
| |
| protected void setUserRole(String user, String role) { |
| ((Map)rules.get("user-role")).put(user, 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); |
| @SuppressWarnings({"rawtypes"}) |
| 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 { |
| @SuppressWarnings({"rawtypes"}) |
| 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)); |
| @SuppressWarnings({"rawtypes"}) |
| LinkedList ll = new LinkedList(); |
| Map<String, Object> edited = plugin.edit(conf, parsedCommands); |
| if(edited!= null) conf = edited; |
| @SuppressWarnings({"rawtypes"}) |
| 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); |
| } |
| } |
| |
| void checkRules(Map<String, Object> values, int expected) { |
| checkRules(values, expected, rules); |
| } |
| |
| void checkRules(Map<String, Object> values, int expected, Map<String, Object> permissions) { |
| AuthorizationContext context = getMockContext(values); |
| try (RuleBasedAuthorizationPluginBase plugin = createPlugin()) { |
| plugin.init(permissions); |
| AuthorizationResponse authResp = plugin.authorize(context); |
| assertEquals(expected, authResp.statusCode); |
| } catch (IOException e) { |
| ; // swallow error, otherwise a you have to add a _lot_ of exceptions to methods. |
| } |
| } |
| |
| protected RuleBasedAuthorizationPluginBase createPlugin() { |
| return new RuleBasedAuthorizationPlugin(); |
| } |
| |
| AuthorizationContext getMockContext(Map<String, Object> values) { |
| return new MockAuthorizationContext(values) { |
| @Override |
| public Principal getUserPrincipal() { |
| Object userPrincipal = values.get("userPrincipal"); |
| return userPrincipal == null ? null : new BasicUserPrincipal(String.valueOf(userPrincipal)); |
| } |
| }; |
| } |
| |
| protected abstract class MockAuthorizationContext extends AuthorizationContext { |
| private final Map<String,Object> values; |
| |
| public 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<>()) : params; |
| } |
| |
| @Override |
| public String getHttpHeader(String header) { |
| return null; |
| } |
| |
| @Override |
| @SuppressWarnings({"rawtypes"}) |
| 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"); |
| } |
| } |
| } |