From b6ca68e933b152e3598c5746fd0bc113c42af34c Mon Sep 17 00:00:00 2001 From: g0w6y Date: Sun, 17 May 2026 14:04:11 +0530 Subject: [PATCH] fix(core): enforce class-level HTTP method annotations for wildcard-resolved unannotated methods The WW-5535 fix (commit 4d2eb93) corrected isMethodSpecified() for wildcard-resolved methods but introduced a structural gap in HttpMethodInterceptor.intercept(). The if/else-if structure made the class-level annotation check unreachable whenever isMethodSpecified()=true and the resolved method carries no method-level annotation: if (isMethodSpecified()) { if (isAnnotatedBy(method)) { ... } // falls through silently } else if (isAnnotatedBy(class)) { ... } // never reached return invocation.invoke(); // no enforcement Fix: convert else-if to standalone if so the class-level check is always evaluated as a fallback when the method itself has no annotation. Method-level annotations still take precedence (checked first). Add two regression tests covering the wildcard-resolved unannotated method scenario. --- .../httpmethod/HttpMethodInterceptor.java | 3 +- .../httpmethod/HttpMethodInterceptorTest.java | 45 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/apache/struts2/interceptor/httpmethod/HttpMethodInterceptor.java b/core/src/main/java/org/apache/struts2/interceptor/httpmethod/HttpMethodInterceptor.java index cc2dd4cc8f..8e732f3260 100644 --- a/core/src/main/java/org/apache/struts2/interceptor/httpmethod/HttpMethodInterceptor.java +++ b/core/src/main/java/org/apache/struts2/interceptor/httpmethod/HttpMethodInterceptor.java @@ -90,7 +90,8 @@ public String intercept(ActionInvocation invocation) throws Exception { invocation.getProxy().getMethod(), AllowedHttpMethod.class.getSimpleName(), request.getMethod()); return doIntercept(invocation, method); } - } else if (AnnotationUtils.isAnnotatedBy(action.getClass(), HTTP_METHOD_ANNOTATIONS)) { + } + if (AnnotationUtils.isAnnotatedBy(action.getClass(), HTTP_METHOD_ANNOTATIONS)) { LOG.debug("Action: {} annotated with: {}, checking if request: {} meets allowed methods!", action, AllowedHttpMethod.class.getSimpleName(), request.getMethod()); return doIntercept(invocation, action.getClass()); diff --git a/core/src/test/java/org/apache/struts2/interceptor/httpmethod/HttpMethodInterceptorTest.java b/core/src/test/java/org/apache/struts2/interceptor/httpmethod/HttpMethodInterceptorTest.java index 5b2548479a..ef67d65ab9 100644 --- a/core/src/test/java/org/apache/struts2/interceptor/httpmethod/HttpMethodInterceptorTest.java +++ b/core/src/test/java/org/apache/struts2/interceptor/httpmethod/HttpMethodInterceptorTest.java @@ -273,6 +273,51 @@ private void prepareActionInvocation(Object action) { invocation.setProxy(actionProxy); } + + /** + * Regression: wildcard-resolved method with NO method-level annotation on a class + * that has a class-level @AllowedHttpMethod(POST) — GET must be rejected. + * The WW-5535 fix introduced an if/else-if that made the class-level check + * unreachable when isMethodSpecified()=true and the method is unannotated. + */ + public void testWildcardResolvedUnannotatedMethodRespectsClassLevelAnnotation() throws Exception { + // given — HttpMethodsTestAction has @AllowedHttpMethod(POST) at class level + // execute() inherited from ActionSupport has no method-level HTTP annotation + HttpMethodsTestAction action = new HttpMethodsTestAction(); + prepareActionInvocation(action); + actionProxy.setMethod("execute"); + actionProxy.setMethodSpecified(true); // simulates wildcard-resolved, not default + + prepareRequest("get"); + + // when + String resultName = interceptor.intercept(invocation); + + // then — class-level @AllowedHttpMethod(POST) must still be enforced + assertEquals("bad-request", resultName); + } + + /** + * Counterpart: POST on wildcard-resolved unannotated method must succeed + * when the class allows POST via class-level annotation. + */ + public void testWildcardResolvedUnannotatedMethodAllowsPostWithClassLevelAnnotation() throws Exception { + // given + HttpMethodsTestAction action = new HttpMethodsTestAction(); + prepareActionInvocation(action); + actionProxy.setMethod("execute"); + actionProxy.setMethodSpecified(true); + invocation.setResultCode("success"); + + prepareRequest("post"); + + // when + String resultName = interceptor.intercept(invocation); + + // then + assertEquals("success", resultName); + } + private void prepareRequest(String httpMethod) { MockHttpServletRequest request = new MockHttpServletRequest(httpMethod, "/action"); ActionContext.getContext().withServletRequest(request);