This is an automated email from the ASF dual-hosted git repository.

lprimak pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/shiro.git


The following commit(s) were added to refs/heads/main by this push:
     new a98e46c19 bugfix(Guice): align Guice behavior regarding superclass 
annotation p… (#2881)
a98e46c19 is described below

commit a98e46c1944b03b316dac880f876fb16cf4c8ca7
Author: Lenny Primak <[email protected]>
AuthorDate: Tue Sep 8 11:36:27 2026 -0500

    bugfix(Guice): align Guice behavior regarding superclass annotation p… 
(#2881)
---
 .../shiro/aop/DefaultAnnotationResolver.java       |  2 +-
 .../org/apache/shiro/guice/aop/ShiroAopModule.java |  3 +-
 .../aop/ClassLevelSuperclassAnnotatedTest.java     | 98 ++++++++++++++++++++++
 3 files changed, 101 insertions(+), 2 deletions(-)

diff --git 
a/core/src/main/java/org/apache/shiro/aop/DefaultAnnotationResolver.java 
b/core/src/main/java/org/apache/shiro/aop/DefaultAnnotationResolver.java
index c7784836b..3380beba2 100644
--- a/core/src/main/java/org/apache/shiro/aop/DefaultAnnotationResolver.java
+++ b/core/src/main/java/org/apache/shiro/aop/DefaultAnnotationResolver.java
@@ -68,7 +68,7 @@ public class DefaultAnnotationResolver implements 
AnnotationResolver {
         return annotation;
     }
 
-    private Annotation getAnnotationFromClassHierarchy(Class<?> targetClass, 
Class<? extends Annotation> clazz) {
+    public static Annotation getAnnotationFromClassHierarchy(Class<?> 
targetClass, Class<? extends Annotation> clazz) {
         Class<?> current = targetClass;
         while (current != null) {
             Annotation annotation = current.getDeclaredAnnotation(clazz);
diff --git 
a/support/guice/src/main/java/org/apache/shiro/guice/aop/ShiroAopModule.java 
b/support/guice/src/main/java/org/apache/shiro/guice/aop/ShiroAopModule.java
index e26f73695..39500d014 100644
--- a/support/guice/src/main/java/org/apache/shiro/guice/aop/ShiroAopModule.java
+++ b/support/guice/src/main/java/org/apache/shiro/guice/aop/ShiroAopModule.java
@@ -30,6 +30,7 @@ import 
org.apache.shiro.authz.aop.RoleAnnotationMethodInterceptor;
 import org.apache.shiro.authz.aop.UserAnnotationMethodInterceptor;
 
 import java.lang.annotation.Annotation;
+import static 
org.apache.shiro.aop.DefaultAnnotationResolver.getAnnotationFromClassHierarchy;
 
 /**
  * Install this module to enable Shiro AOP functionality in Guice.  You may 
extend it to add your own Shiro
@@ -47,7 +48,7 @@ public class ShiroAopModule extends AbstractModule {
         bindInterceptor(Matchers.any(), method -> {
             Class<? extends Annotation> annotation = 
methodInterceptor.getHandler().getAnnotationClass();
             return method.getAnnotation(annotation) != null
-                    || method.getDeclaringClass().getAnnotation(annotation) != 
null;
+                    || 
getAnnotationFromClassHierarchy(method.getDeclaringClass(), annotation) != null;
         }, new AopAllianceMethodInterceptorAdapter(methodInterceptor));
     }
 
diff --git 
a/support/guice/src/test/java/org/apache/shiro/guice/aop/ClassLevelSuperclassAnnotatedTest.java
 
b/support/guice/src/test/java/org/apache/shiro/guice/aop/ClassLevelSuperclassAnnotatedTest.java
new file mode 100644
index 000000000..844177818
--- /dev/null
+++ 
b/support/guice/src/test/java/org/apache/shiro/guice/aop/ClassLevelSuperclassAnnotatedTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.shiro.guice.aop;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.authz.annotation.RequiresRoles;
+import org.apache.shiro.mgt.DefaultSecurityManager;
+import org.apache.shiro.realm.SimpleAccountRealm;
+import org.apache.shiro.subject.Subject;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class ClassLevelSuperclassAnnotatedTest {
+    private final Injector injector = Guice.createInjector(new 
ShiroAopModule());
+
+    @Test
+    void overriddenMethodExecutesForUserWithoutRequiredRole() {
+        Subject subject = login("alice");
+        OverridingService service = 
injector.getInstance(OverridingService.class);
+        subject.execute(() -> {
+            assertThatThrownBy(service::adminOnlyData)
+                    .isInstanceOf(AuthorizationException.class);
+
+            assertThatThrownBy(service::directlyProtected)
+                    .isInstanceOf(AuthorizationException.class);
+        });
+    }
+
+    @Test
+    void inheritedMethodStillExecutesTheSuperclassCheck() {
+        Subject subject = login("alice");
+        InheritedService service = 
injector.getInstance(InheritedService.class);
+        subject.execute(() -> {
+            assertThatThrownBy(service::adminOnlyData)
+                    .isInstanceOf(AuthorizationException.class);
+        });
+    }
+
+    @Test
+    void adminCanExecuteTheOverriddenMethod() {
+        Subject subject = login("admin");
+        OverridingService service = 
injector.getInstance(OverridingService.class);
+        subject.execute(() -> {
+            assertThat(service.adminOnlyData()).isEqualTo("ADMIN_ONLY_DATA");
+        });
+    }
+
+    private Subject login(String username) {
+        SimpleAccountRealm realm = new SimpleAccountRealm();
+        realm.addAccount("alice", "pw", "user");
+        realm.addAccount("admin", "pw", "admin");
+        Subject subject = new Subject.Builder(new 
DefaultSecurityManager(realm)).buildSubject();
+        subject.login(new UsernamePasswordToken(username, "pw"));
+        return subject;
+    }
+
+    @RequiresRoles("admin")
+    public static class ParentService {
+        public String adminOnlyData() {
+            return "PARENT_ADMIN_ONLY_DATA";
+        }
+    }
+
+    public static class OverridingService extends ParentService {
+        @Override
+        public String adminOnlyData() {
+            return "ADMIN_ONLY_DATA";
+        }
+
+        @RequiresRoles("admin")
+        public void directlyProtected() {
+        }
+    }
+
+    public static class InheritedService extends ParentService {
+    }
+}

Reply via email to