You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shiro.apache.org by bd...@apache.org on 2019/12/05 22:24:27 UTC
[shiro] 01/01: Add support for JSR-250 annotations DenyAll,
PermitAll, RolesAllowed
This is an automated email from the ASF dual-hosted git repository.
bdemers pushed a commit to branch jsr-250
in repository https://gitbox.apache.org/repos/asf/shiro.git
commit c6d547dc6af218f088a06001da8dc0ff8cec7abd
Author: Brian Demers <bd...@apache.org>
AuthorDate: Thu Dec 5 17:23:21 2019 -0500
Add support for JSR-250 annotations DenyAll, PermitAll, RolesAllowed
---
core/pom.xml | 7 +
.../shiro/aop/DefaultAnnotationResolver.java | 3 +
.../AnnotationsAuthorizingMethodInterceptor.java | 7 +-
.../shiro/authz/aop/DenyAllAnnotationHandler.java | 57 +++++++
.../shiro/authz/aop/Jsr250MethodInterceptor.java | 105 ++++++++++++
.../authz/aop/PermitAllAnnotationHandler.java | 49 ++++++
.../authz/aop/RolesAllowedAnnotationHandler.java | 70 ++++++++
.../authz/aop/DenyAllAnnotationHandlerTest.java | 72 ++++++++
.../Jsr250AnnotationHandlerInteractionTest.java | 183 +++++++++++++++++++++
.../authz/aop/PermitAllAnnotationHandlerTest.java | 46 ++++++
.../aop/RolesAllowedAnnotationHandlerTest.java | 97 +++++++++++
11 files changed, 695 insertions(+), 1 deletion(-)
diff --git a/core/pom.xml b/core/pom.xml
index 73e6211..513eb4e 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -57,6 +57,7 @@
org.apache.shiro*;version="${shiro.osgi.importRange}",
org.apache.commons.beanutils*;resolution:=optional,
org.apache.commons.configuration2*;resolution:=optional,
+ javax.annotation.security*;resolution:=optional,
*
</Import-Package>
<DynamicImport-Package>
@@ -98,6 +99,12 @@
<artifactId>shiro-event</artifactId>
</dependency>
+ <dependency>
+ <groupId>javax.annotation</groupId>
+ <artifactId>javax.annotation-api</artifactId>
+ <version>1.3.2</version>
+ </dependency>
+
<!-- Test dependencies -->
<dependency>
<groupId>org.slf4j</groupId>
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 d7fd4ae..84671ca 100644
--- a/core/src/main/java/org/apache/shiro/aop/DefaultAnnotationResolver.java
+++ b/core/src/main/java/org/apache/shiro/aop/DefaultAnnotationResolver.java
@@ -58,7 +58,10 @@ public class DefaultAnnotationResolver implements AnnotationResolver {
throw new IllegalArgumentException(msg);
}
+ // first look for the Annotation on the method
Annotation annotation = m.getAnnotation(clazz);
+
+ // then look on the class
if (annotation == null ) {
Object miThis = mi.getThis();
//SHIRO-473 - miThis could be null for static methods, just return null
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/AnnotationsAuthorizingMethodInterceptor.java b/core/src/main/java/org/apache/shiro/authz/aop/AnnotationsAuthorizingMethodInterceptor.java
index f9bd41a..661d3f3 100644
--- a/core/src/main/java/org/apache/shiro/authz/aop/AnnotationsAuthorizingMethodInterceptor.java
+++ b/core/src/main/java/org/apache/shiro/authz/aop/AnnotationsAuthorizingMethodInterceptor.java
@@ -23,6 +23,7 @@ import java.util.Collection;
import org.apache.shiro.aop.MethodInvocation;
import org.apache.shiro.authz.AuthorizationException;
+import org.apache.shiro.util.ClassUtils;
/**
* An <tt>AnnotationsAuthorizingMethodInterceptor</tt> is a MethodInterceptor that asserts a given method is authorized
@@ -52,12 +53,16 @@ public abstract class AnnotationsAuthorizingMethodInterceptor extends Authorizin
* support role and permission annotations.
*/
public AnnotationsAuthorizingMethodInterceptor() {
- methodInterceptors = new ArrayList<AuthorizingAnnotationMethodInterceptor>(5);
+ methodInterceptors = new ArrayList<>(8);
methodInterceptors.add(new RoleAnnotationMethodInterceptor());
methodInterceptors.add(new PermissionAnnotationMethodInterceptor());
methodInterceptors.add(new AuthenticatedAnnotationMethodInterceptor());
methodInterceptors.add(new UserAnnotationMethodInterceptor());
methodInterceptors.add(new GuestAnnotationMethodInterceptor());
+
+ if (ClassUtils.isAvailable("javax.annotation.security.PermitAll")) {
+ methodInterceptors.add(new Jsr250MethodInterceptor());
+ }
}
/**
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/DenyAllAnnotationHandler.java b/core/src/main/java/org/apache/shiro/authz/aop/DenyAllAnnotationHandler.java
new file mode 100644
index 0000000..0b8fb00
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/DenyAllAnnotationHandler.java
@@ -0,0 +1,57 @@
+/*
+ * 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.authz.aop;
+
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.authz.UnauthorizedException;
+
+import javax.annotation.security.DenyAll;
+import java.lang.annotation.Annotation;
+
+/**
+ * This {@link org.apache.shiro.aop.AnnotationHandler AnnotationHandler} denys access from any subject
+ * (anonymous or logged in user).
+ *
+ * @since 1.5.0
+ */
+public class DenyAllAnnotationHandler extends AuthorizingAnnotationHandler {
+
+
+ /**
+ * Default no-argument constructor that ensures this interceptor looks for
+ *
+ * {@link org.apache.shiro.authz.annotation.RequiresGuest RequiresGuest} annotations in a method
+ * declaration.
+ */
+ public DenyAllAnnotationHandler() {
+ super(DenyAll.class);
+ }
+
+ /**
+ * Causes a {@link UnauthorizedException} to be thrown if a DenyAll annotation is present.
+ *
+ * @param a the annotation to check for one or more roles
+ * @throws UnauthorizedException when the DenyAll annotation is present
+ */
+ public void assertAuthorized(Annotation a) throws UnauthorizedException {
+// if (!(a instanceof DenyAll)) return;
+
+ throw new UnauthenticatedException("Attempting to perform a denied operation.");
+ }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/Jsr250MethodInterceptor.java b/core/src/main/java/org/apache/shiro/authz/aop/Jsr250MethodInterceptor.java
new file mode 100644
index 0000000..ae7b6bc
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/Jsr250MethodInterceptor.java
@@ -0,0 +1,105 @@
+/*
+ * 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.authz.aop;
+
+import org.apache.shiro.aop.MethodInterceptorSupport;
+import org.apache.shiro.aop.MethodInvocation;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+public class Jsr250MethodInterceptor extends AuthorizingAnnotationMethodInterceptor {
+
+ private final Map<Class<? extends Annotation>, AuthorizingAnnotationHandler> handlerMap = new HashMap<>();
+
+ public Jsr250MethodInterceptor() {
+
+ // NOTE: in order for this package to retain backwards compatibility, it MUST extend AuthorizingAnnotationMethodInterceptor
+ // which only supports a single Handler. JSR 250 annotations require knowledge of other annotations (PermitAll on a method
+ // overrides DenyAll at the class level). Using the most restrictive handler here as a place holder.
+ super(new DenyAllAnnotationHandler());
+
+ handlerMap.put(DenyAll.class, new DenyAllAnnotationHandler());
+ handlerMap.put(PermitAll.class, new PermitAllAnnotationHandler());
+ handlerMap.put(RolesAllowed.class, new RolesAllowedAnnotationHandler());
+ }
+
+ @Override
+ public Object invoke(MethodInvocation methodInvocation) throws Throwable {
+ assertAuthorized(methodInvocation);
+ return methodInvocation.proceed();
+ }
+
+ @Override
+ public void assertAuthorized(MethodInvocation methodInvocation) {
+ if (methodInvocation == null) {
+ throw new IllegalArgumentException("method argument cannot be null");
+ }
+
+ Annotation annotation = getAnnotation(methodInvocation);
+ if (annotation != null) {
+ AuthorizingAnnotationHandler handler = handlerMap.get(annotation.annotationType());
+ handler.assertAuthorized(annotation);
+ }
+ }
+
+ @Override
+ public Annotation getAnnotation(MethodInvocation methodInvocation) {
+
+ Method method = methodInvocation.getMethod();
+ if (method == null) {
+ String msg = MethodInvocation.class.getName() + " parameter incorrectly constructed. getMethod() returned null";
+ throw new IllegalArgumentException(msg);
+
+ }
+
+ // look for DenyAll, PermitAll, and AllowedRoles in that order
+ Annotation annotation = method.getAnnotation(DenyAll.class);
+
+ if (annotation == null) {
+ annotation = method.getAnnotation(PermitAll.class);
+ }
+
+ if (annotation == null) {
+ annotation = method.getAnnotation(RolesAllowed.class);
+ }
+
+ // if still null check at the class level
+ Object miThis = methodInvocation.getThis();
+ if (annotation == null && miThis != null) {
+
+ annotation = miThis.getClass().getAnnotation(DenyAll.class);
+
+ if (annotation == null) {
+ annotation = miThis.getClass().getAnnotation(PermitAll.class);
+ }
+
+ if (annotation == null) {
+ annotation = miThis.getClass().getAnnotation(RolesAllowed.class);
+ }
+ }
+
+ return annotation;
+ }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/PermitAllAnnotationHandler.java b/core/src/main/java/org/apache/shiro/authz/aop/PermitAllAnnotationHandler.java
new file mode 100644
index 0000000..3f8d2b6
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/PermitAllAnnotationHandler.java
@@ -0,0 +1,49 @@
+/*
+ * 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.authz.aop;
+
+import javax.annotation.security.PermitAll;
+import java.lang.annotation.Annotation;
+
+/**
+ * This {@link org.apache.shiro.aop.AnnotationHandler AnnotationHandler} allows access from any subject
+ * (anonymous or logged in user). It is largely a no-op or for documentation. However, this annotation WILL override
+ * a {@link javax.annotation.security.DenyAll DenyAll} or {@link javax.annotation.security.RolesAllowed RolesAllowed}
+ * annotation if those annotations are placed at the class level and {@link PermitAll} is placed on a method.
+ *
+ * @since 1.5.0
+ */
+public class PermitAllAnnotationHandler extends AuthorizingAnnotationHandler {
+
+
+ /**
+ * Default no-argument constructor that ensures this interceptor looks for a {@link PermitAll}
+ * annotation in a method declaration.
+ */
+ public PermitAllAnnotationHandler() {
+ super(PermitAll.class);
+ }
+
+ /**
+ * No-op, the {@link PermitAll} annotation allows all subjects (including guests/anonymous).
+ *
+ * @param a the annotation to check for one or more roles
+ */
+ public void assertAuthorized(Annotation a) { }
+}
diff --git a/core/src/main/java/org/apache/shiro/authz/aop/RolesAllowedAnnotationHandler.java b/core/src/main/java/org/apache/shiro/authz/aop/RolesAllowedAnnotationHandler.java
new file mode 100644
index 0000000..ec1ebcc
--- /dev/null
+++ b/core/src/main/java/org/apache/shiro/authz/aop/RolesAllowedAnnotationHandler.java
@@ -0,0 +1,70 @@
+/*
+ * 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.authz.aop;
+
+import org.apache.shiro.authz.AuthorizationException;
+
+import javax.annotation.security.RolesAllowed;
+import java.lang.annotation.Annotation;
+
+/**
+ * Checks to see if a @{@link RolesAllowed} annotation is declared, and if so, performs
+ * a role check to see if the calling <code>Subject</code> is allowed to proceed.
+ *
+ * @since 1.5.0
+ */
+public class RolesAllowedAnnotationHandler extends AuthorizingAnnotationHandler {
+
+ /**
+ * Default no-argument constructor that ensures this handler looks for
+ * {@link org.apache.shiro.authz.annotation.RequiresRoles RequiresRoles} annotations.
+ */
+ public RolesAllowedAnnotationHandler() {
+ super(RolesAllowed.class);
+ }
+
+ /**
+ * Ensures that the calling <code>Subject</code> has one of the Annotation's specified roles, and if not, throws an
+ * <code>AuthorizingException</code> indicating that access is denied.
+ *
+ * @param a the RolesAllowed annotation to use to check for one or more roles
+ * @throws org.apache.shiro.authz.AuthorizationException
+ * if the calling <code>Subject</code> does not have the role necessary to
+ * proceed.
+ */
+ public void assertAuthorized(Annotation a) throws AuthorizationException {
+ if (!(a instanceof RolesAllowed)) return;
+
+ RolesAllowed raAnnotation = (RolesAllowed) a;
+ String[] roles = raAnnotation.value();
+
+ if (roles.length == 1) {
+ getSubject().checkRole(roles[0]);
+ return;
+ }
+
+ // Logical OR
+
+ // Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first
+ boolean hasAtLeastOneRole = false;
+ for (String role : roles) if (getSubject().hasRole(role)) hasAtLeastOneRole = true;
+ // Cause the exception if none of the role match, note that the exception message will be a bit misleading
+ if (!hasAtLeastOneRole) getSubject().checkRole(roles[0]);
+ }
+}
\ No newline at end of file
diff --git a/core/src/test/java/org/apache/shiro/authz/aop/DenyAllAnnotationHandlerTest.java b/core/src/test/java/org/apache/shiro/authz/aop/DenyAllAnnotationHandlerTest.java
new file mode 100644
index 0000000..27a4629
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authz/aop/DenyAllAnnotationHandlerTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.authz.aop;
+
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.test.SecurityManagerTestSupport;
+import org.junit.Test;
+
+import javax.annotation.security.DenyAll;
+import java.lang.annotation.Annotation;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+
+/**
+ * Test cases for the {@link DenyAllAnnotationHandler} class.
+ */
+public class DenyAllAnnotationHandlerTest extends SecurityManagerTestSupport {
+ private Subject subject;
+
+ @Test(expected = UnauthenticatedException.class)
+ public void testGuestSingleRoleAssertion() throws Throwable {
+ DenyAllAnnotationHandler handler = new DenyAllAnnotationHandler();
+
+ Annotation denyAllAnnotation = new DenyAll() {
+ public Class<? extends Annotation> annotationType() {
+ return DenyAll.class;
+ }
+ };
+
+ handler.assertAuthorized(denyAllAnnotation);
+ }
+
+ @Test(expected = UnauthenticatedException.class)
+ public void testOneOfTheRolesRequired() throws Throwable {
+ subject = createMock(Subject.class);
+ expect(subject.hasRole("blah")).andReturn(true);
+ replay(subject);
+ DenyAllAnnotationHandler handler = new DenyAllAnnotationHandler() {
+ @Override
+ protected Subject getSubject() {
+ return subject;
+ }
+ };
+
+ Annotation denyAllAnnotation = new DenyAll() {
+
+ public Class<? extends Annotation> annotationType() {
+ return DenyAll.class;
+ }
+ };
+ handler.assertAuthorized(denyAllAnnotation);
+ }
+}
diff --git a/core/src/test/java/org/apache/shiro/authz/aop/Jsr250AnnotationHandlerInteractionTest.java b/core/src/test/java/org/apache/shiro/authz/aop/Jsr250AnnotationHandlerInteractionTest.java
new file mode 100644
index 0000000..ae80ea9
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authz/aop/Jsr250AnnotationHandlerInteractionTest.java
@@ -0,0 +1,183 @@
+package org.apache.shiro.authz.aop;
+
+import org.apache.shiro.aop.MethodInvocation;
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.test.SecurityManagerTestSupport;
+import org.apache.shiro.util.ThreadContext;
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+import javax.annotation.security.DenyAll;
+import javax.annotation.security.PermitAll;
+import javax.annotation.security.RolesAllowed;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+
+public class Jsr250AnnotationHandlerInteractionTest extends SecurityManagerTestSupport {
+
+ @Test(expected = UnauthenticatedException.class)
+ public void denyClassNoAnnotation() throws Throwable {
+ createSubject();
+ invokeWithInterceptor(new ExampleDenyAtClass(), "expectDenied");
+ }
+
+ @Test
+ public void denyClassPermitMethod() throws Throwable {
+ createSubject();
+ invokeWithInterceptor(new ExampleDenyAtClass(), "expectAllowed");
+ }
+
+ @Test
+ public void denyClassRolesAllowedAnnotation() throws Throwable {
+ createSubject("foo");
+ invokeWithInterceptor(new ExampleDenyAtClass(), "withRoles");
+ }
+
+ @Test(expected = UnauthenticatedException.class)
+ public void denyClassRolesAllowedAnnotation_noRoles() throws Throwable {
+ createSubject();
+ invokeWithInterceptor(new ExampleDenyAtClass(), "withRoles");
+ }
+
+ @Test(expected = UnauthenticatedException.class)
+ public void permitClassNoAnnotation() throws Throwable {
+ createSubject();
+ invokeWithInterceptor(new ExamplePermitAtClass(), "expectDenied");
+ }
+
+ @Test
+ public void permitClassPermitMethod() throws Throwable {
+ createSubject();
+ invokeWithInterceptor(new ExamplePermitAtClass(), "expectAllowed");
+ }
+
+ @Test
+ public void permitClassRolesAllowedAnnotation() throws Throwable {
+ createSubject("foo");
+ invokeWithInterceptor(new ExamplePermitAtClass(), "withRoles");
+ }
+
+ @Test(expected = UnauthenticatedException.class)
+ public void permitClassRolesAllowedAnnotation_noRoles() throws Throwable {
+ createSubject();
+ invokeWithInterceptor(new ExamplePermitAtClass(), "withRoles");
+ }
+
+ @Test(expected = UnauthenticatedException.class)
+ public void rolesAllowedNoAnnotation() throws Throwable {
+ createSubject();
+ invokeWithInterceptor(new ExampleRolesAllowedAtClass(), "expectDenied");
+ }
+
+ @Test
+ public void rolesAllowedPermitMethod() throws Throwable {
+ createSubject();
+ invokeWithInterceptor(new ExampleRolesAllowedAtClass(), "expectAllowed");
+ }
+
+ @Test
+ public void rolesAllowedRolesAllowedAnnotation() throws Throwable {
+ createSubject("foo");
+ invokeWithInterceptor(new ExampleRolesAllowedAtClass(), "withRoles");
+ }
+
+ @Test(expected = UnauthenticatedException.class)
+ public void rolesAllowedRolesAllowedAnnotation_noRoles() throws Throwable {
+ createSubject();
+ invokeWithInterceptor(new ExampleRolesAllowedAtClass(), "withRoles");
+ }
+
+ private void invokeWithInterceptor(Object target, String methodName) throws Throwable {
+ MethodInvocation mi = new StubMethodInvocation(target.getClass().getDeclaredMethod(methodName), target);
+ new Jsr250MethodInterceptor().invoke(mi);
+ }
+
+ private Subject createSubject(String... roles) {
+ Subject subject = createMock(Subject.class);
+ Arrays.stream(roles).forEach(role -> {
+ expect(subject.hasRole(role)).andReturn(true).anyTimes();
+ subject.checkRole(role);
+ EasyMock.expectLastCall().anyTimes();
+ });
+ expect(subject.hasRole(anyString())).andReturn(false).anyTimes();
+ subject.checkRole(anyString());
+ EasyMock.expectLastCall().andThrow(new UnauthenticatedException("Test thrown authz exception")).anyTimes();
+ replay(subject);
+ ThreadContext.bind(subject);
+ return subject;
+ }
+
+ @DenyAll
+ static class ExampleDenyAtClass {
+
+ void expectDenied() {}
+
+ @PermitAll
+ void expectAllowed() {}
+
+ @RolesAllowed({"blah2", "foo"})
+ void withRoles() {}
+ }
+
+ @PermitAll
+ static class ExamplePermitAtClass {
+
+ @DenyAll
+ void expectDenied() {}
+
+ void expectAllowed() {}
+
+ @RolesAllowed({"blah2", "foo"})
+ void withRoles() {}
+ }
+
+ @RolesAllowed({"blah2", "foo"})
+ static class ExampleRolesAllowedAtClass {
+
+ @DenyAll
+ void expectDenied() {}
+
+ @PermitAll
+ void expectAllowed() {}
+
+ void withRoles() {}
+ }
+
+ static class StubMethodInvocation implements MethodInvocation {
+
+ private final Method method;
+ private final Object target;
+
+ StubMethodInvocation(Method method, Object target) {
+ this.method = method;
+ this.target = target;
+ }
+
+ @Override
+ public Object proceed() throws Throwable {
+ return getMethod();
+ }
+
+ @Override
+ public Method getMethod() {
+ return method;
+ }
+
+ @Override
+ public Object[] getArguments() {
+ // returning params here instead of the actual objects
+ return method.getParameters();
+ }
+
+ @Override
+ public Object getThis() {
+ return target;
+ }
+ }
+}
diff --git a/core/src/test/java/org/apache/shiro/authz/aop/PermitAllAnnotationHandlerTest.java b/core/src/test/java/org/apache/shiro/authz/aop/PermitAllAnnotationHandlerTest.java
new file mode 100644
index 0000000..219f58b
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authz/aop/PermitAllAnnotationHandlerTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.authz.aop;
+
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.test.SecurityManagerTestSupport;
+import org.junit.Test;
+
+import javax.annotation.security.PermitAll;
+import java.lang.annotation.Annotation;
+
+/**
+ * Test cases for the {@link PermitAllAnnotationHandler} class.
+ */
+public class PermitAllAnnotationHandlerTest extends SecurityManagerTestSupport {
+ private Subject subject;
+
+ @Test
+ public void testPermitAll() throws Throwable {
+ PermitAllAnnotationHandler handler = new PermitAllAnnotationHandler();
+
+ Annotation permitallAnnotation = new PermitAll() {
+ public Class<? extends Annotation> annotationType() {
+ return PermitAll.class;
+ }
+ };
+
+ handler.assertAuthorized(permitallAnnotation);
+ }
+}
diff --git a/core/src/test/java/org/apache/shiro/authz/aop/RolesAllowedAnnotationHandlerTest.java b/core/src/test/java/org/apache/shiro/authz/aop/RolesAllowedAnnotationHandlerTest.java
new file mode 100644
index 0000000..ea9678e
--- /dev/null
+++ b/core/src/test/java/org/apache/shiro/authz/aop/RolesAllowedAnnotationHandlerTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.authz.aop;
+
+import org.apache.shiro.authz.UnauthenticatedException;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.test.SecurityManagerTestSupport;
+import org.junit.Test;
+
+import javax.annotation.security.RolesAllowed;
+import java.lang.annotation.Annotation;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+
+/**
+ * Test cases for the {@link RolesAllowedAnnotationHandler} class.
+ */
+public class RolesAllowedAnnotationHandlerTest extends SecurityManagerTestSupport {
+ private Subject subject;
+
+ @Test(expected = UnauthenticatedException.class)
+ public void testGuestSingleRoleAssertion() throws Throwable {
+ RolesAllowedAnnotationHandler handler = new RolesAllowedAnnotationHandler();
+
+ Annotation rolesAllowedAnnotation = new RolesAllowed() {
+ public String[] value() {
+ return new String[]{"blah"};
+ }
+
+ public Class<? extends Annotation> annotationType() {
+ return RolesAllowed.class;
+ }
+ };
+
+ handler.assertAuthorized(rolesAllowedAnnotation);
+ }
+
+ @Test(expected = UnauthenticatedException.class)
+ public void testGuestMultipleRolesAssertion() throws Throwable {
+ RolesAllowedAnnotationHandler handler = new RolesAllowedAnnotationHandler();
+
+ Annotation rolesAllowedAnnotation = new RolesAllowed() {
+ public String[] value() {
+ return new String[]{"blah", "blah2"};
+ }
+
+ public Class<? extends Annotation> annotationType() {
+ return RolesAllowed.class;
+ }
+ };
+
+ handler.assertAuthorized(rolesAllowedAnnotation);
+ }
+
+ @Test
+ public void testOneOfTheRolesRequired() throws Throwable {
+ subject = createMock(Subject.class);
+ expect(subject.hasRole("blah")).andReturn(true);
+ expect(subject.hasRole("blah2")).andReturn(false);
+ replay(subject);
+ RolesAllowedAnnotationHandler handler = new RolesAllowedAnnotationHandler() {
+ @Override
+ protected Subject getSubject() {
+ return subject;
+ }
+ };
+
+ Annotation rolesAllowedAnnotation = new RolesAllowed() {
+ public String[] value() {
+ return new String[]{"blah", "blah2"};
+ }
+
+ public Class<? extends Annotation> annotationType() {
+ return RolesAllowed.class;
+ }
+ };
+ handler.assertAuthorized(rolesAllowedAnnotation);
+ }
+}