This is an automated email from the ASF dual-hosted git repository. coheigea pushed a commit to branch coheigea/SimpleAuthorizingInterceptor in repository https://gitbox.apache.org/repos/asf/cxf.git
commit 15bcfc8ee01723f247ee3a7c4a50e55066c867d2 Author: Colm O hEigeartaigh <[email protected]> AuthorDate: Fri Oct 23 13:45:38 2026 +0100 Make SimpleAuthorizingInterceptor signature keys whitespace-insensitive --- .../security/SimpleAuthorizingInterceptor.java | 52 +++++++++++++++++- .../security/SimpleAuthorizingInterceptorTest.java | 62 ++++++++++++++++++++++ 2 files changed, 112 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/apache/cxf/interceptor/security/SimpleAuthorizingInterceptor.java b/core/src/main/java/org/apache/cxf/interceptor/security/SimpleAuthorizingInterceptor.java index 2fd195245f8..5b73d24be81 100644 --- a/core/src/main/java/org/apache/cxf/interceptor/security/SimpleAuthorizingInterceptor.java +++ b/core/src/main/java/org/apache/cxf/interceptor/security/SimpleAuthorizingInterceptor.java @@ -24,11 +24,19 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.security.SecurityContext; public class SimpleAuthorizingInterceptor extends AbstractAuthorizingInInterceptor { + private static final Logger LOG = LogUtils.getL7dLogger(SimpleAuthorizingInterceptor.class); + private static final Set<String> PRIMITIVE_TYPES = Set.of( + "boolean", "byte", "char", "short", "int", "long", "float", "double"); + private static final Pattern WHITESPACE = Pattern.compile("\\s+"); protected Map<String, List<String>> methodRolesMap = new HashMap<>(); protected Map<String, List<String>> userRolesMap = Collections.emptyMap(); @@ -80,7 +88,11 @@ public class SimpleAuthorizingInterceptor extends AbstractAuthorizingInIntercept @Override protected List<String> getExpectedRoles(Method method) { - List<String> roles = methodRolesMap.get(createMethodSig(method)); + String sig = createMethodSig(method); + List<String> roles = methodRolesMap.get(sig); + if (roles == null) { + roles = methodRolesMap.get(normalizeMethodSig(sig)); + } if (roles == null) { roles = methodRolesMap.get(method.getName()); } @@ -92,7 +104,11 @@ public class SimpleAuthorizingInterceptor extends AbstractAuthorizingInIntercept public void setMethodRolesMap(Map<String, String> rolesMap) { - methodRolesMap.putAll(parseRolesMap(rolesMap)); + for (Map.Entry<String, List<String>> entry : parseRolesMap(rolesMap).entrySet()) { + String key = normalizeMethodSig(entry.getKey()); + warnOnUnmatchableSig(key); + methodRolesMap.put(key, entry.getValue()); + } } public void setUserRolesMap(Map<String, String> rolesMap) { @@ -107,6 +123,38 @@ public class SimpleAuthorizingInterceptor extends AbstractAuthorizingInIntercept this.checkConfiguredRolesOnly = checkConfiguredRolesOnly; } + /** + * Brings a method signature key into the format produced by {@link #createMethodSig(Method)}, + * e.g. "void op(java.lang.String, long)", so that whitespace differences such as + * "op(java.lang.String,long)" do not prevent a configured role rule from matching. + * Keys that are not signatures are returned as is. + */ + private static String normalizeMethodSig(String key) { + int open = key.indexOf('('); + int close = key.lastIndexOf(')'); + if (open == -1 || close < open) { + return key; + } + String prefix = WHITESPACE.matcher(key.substring(0, open).trim()).replaceAll(" "); + String params = WHITESPACE.matcher(key.substring(open + 1, close)).replaceAll(""); + return prefix + '(' + params.replace(",", ", ") + ')'; + } + + private static void warnOnUnmatchableSig(String key) { + int open = key.indexOf('('); + if (open == -1 || open + 1 >= key.length() - 1) { + return; + } + for (String param : key.substring(open + 1, key.length() - 1).split(", ")) { + // Class.getName() yields "java.lang.String", "long" or "[I", never "String" or "int[]" + if (param.endsWith("[]") + || param.indexOf('.') == -1 && !param.startsWith("[") && !PRIMITIVE_TYPES.contains(param)) { + LOG.warning("Method roles key \"" + key + "\" has the parameter type \"" + param + + "\" which is not a Class.getName() type name, the key will not match any method"); + } + } + } + private static Map<String, List<String>> parseRolesMap(Map<String, String> rolesMap) { Map<String, List<String>> map = new HashMap<>(); for (Map.Entry<String, String> entry : rolesMap.entrySet()) { diff --git a/core/src/test/java/org/apache/cxf/interceptor/security/SimpleAuthorizingInterceptorTest.java b/core/src/test/java/org/apache/cxf/interceptor/security/SimpleAuthorizingInterceptorTest.java index a5341736e24..a4252adeef3 100644 --- a/core/src/test/java/org/apache/cxf/interceptor/security/SimpleAuthorizingInterceptorTest.java +++ b/core/src/test/java/org/apache/cxf/interceptor/security/SimpleAuthorizingInterceptorTest.java @@ -201,6 +201,56 @@ public class SimpleAuthorizingInterceptorTest { assertEquals(Collections.singletonList("admin"), in.getExpectedRoles(transfer)); } + @Test + public void testMethodSignatureRolesIgnoreWhitespace() throws Exception { + Method transfer = TestService.class.getMethod("transfer", String.class, long.class); + for (String key : new String[] {"void transfer(java.lang.String,long)", + " void transfer( java.lang.String , long ) ", "void transfer(java.lang.String,\tlong)"}) { + SimpleAuthorizingInterceptor in = new SimpleAuthorizingInterceptor(); + in.setMethodRolesMap(Collections.singletonMap(key, "admin")); + assertEquals(key, Collections.singletonList("admin"), in.getExpectedRoles(transfer)); + } + } + + @Test + public void testMethodSignatureRolesAddedDirectly() throws Exception { + Method transfer = TestService.class.getMethod("transfer", String.class, long.class); + SimpleAuthorizingInterceptor in = new SimpleAuthorizingInterceptor(); + in.methodRolesMap.put(in.createMethodSig(transfer), Collections.singletonList("admin")); + assertEquals(Collections.singletonList("admin"), in.getExpectedRoles(transfer)); + } + + @Test + public void testCustomMethodSigAddedDirectly() throws Exception { + Method transfer = TestService.class.getMethod("transfer", String.class, long.class); + SimpleAuthorizingInterceptor in = new SimpleAuthorizingInterceptor() { + @Override + protected String createMethodSig(Method m) { + return super.createMethodSig(m).replace(", ", ","); + } + }; + in.methodRolesMap.put(in.createMethodSig(transfer), Collections.singletonList("admin")); + assertEquals(Collections.singletonList("admin"), in.getExpectedRoles(transfer)); + } + + @Test + public void testMethodSignatureRolesNoParameters() throws Exception { + Method echo = TestService.class.getMethod("echo"); + SimpleAuthorizingInterceptor in = new SimpleAuthorizingInterceptor(); + in.setMethodRolesMap(Collections.singletonMap("void echo( )", "admin")); + assertEquals(Collections.singletonList("admin"), in.getExpectedRoles(echo)); + } + + @Test + public void testSecureAnnotationsSignatureRoles() throws Exception { + SecureAnnotationsInterceptor in = new SecureAnnotationsInterceptor(); + in.setSecuredObject(new AnnotatedService()); + assertEquals(Collections.singletonList("admin"), + in.getExpectedRoles(AnnotatedService.class.getMethod("transfer", String.class, long.class))); + assertEquals(Collections.singletonList("user"), + in.getExpectedRoles(AnnotatedService.class.getMethod("transfer", String.class))); + } + private static final class TestService { @SuppressWarnings("unused") public void echo() { @@ -213,6 +263,18 @@ public class SimpleAuthorizingInterceptorTest { } } + public static class AnnotatedService { + @jakarta.annotation.security.RolesAllowed("admin") + public void transfer(String account, long amount) { + + } + + @jakarta.annotation.security.RolesAllowed("user") + public void transfer(String account) { + + } + } + private static final class IncompleteSecurityContext implements SecurityContext { public Principal getUserPrincipal() {
