SHIRO-348, Adding boolean to toggle if exceptions should be thrown or ignored.

git-svn-id: https://svn.apache.org/repos/asf/shiro/branches/exceptionCatchingModularRealmAuthorizer@1298870 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/core/src/main/java/org/apache/shiro/authz/ModularRealmAuthorizer.java b/core/src/main/java/org/apache/shiro/authz/ModularRealmAuthorizer.java
index 4a12024..0b99841 100644
--- a/core/src/main/java/org/apache/shiro/authz/ModularRealmAuthorizer.java
+++ b/core/src/main/java/org/apache/shiro/authz/ModularRealmAuthorizer.java
@@ -18,12 +18,15 @@
  */
 package org.apache.shiro.authz;
 
+import org.apache.shiro.ShiroException;
 import org.apache.shiro.authz.permission.PermissionResolver;
 import org.apache.shiro.authz.permission.PermissionResolverAware;
 import org.apache.shiro.authz.permission.RolePermissionResolver;
 import org.apache.shiro.authz.permission.RolePermissionResolverAware;
 import org.apache.shiro.realm.Realm;
 import org.apache.shiro.subject.PrincipalCollection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.util.Collection;
 import java.util.List;
@@ -38,6 +41,11 @@
 public class ModularRealmAuthorizer implements Authorizer, PermissionResolverAware, RolePermissionResolverAware {
 
     /**
+     * Logger
+     */
+    private static final Logger log = LoggerFactory.getLogger( ModularRealmAuthorizer.class );
+    
+    /**
      * The realms to consult during any authorization check.
      */
     protected Collection<Realm> realms;
@@ -55,6 +63,13 @@
     protected RolePermissionResolver rolePermissionResolver;
 
     /**
+     * A boolean which can be set to ignore Exceptions thrown from AuthorizationRealms.  Useful when multiple
+     * realms are configured, and one of them throws Exceptions, and you want other realms to be consulted. <BR/>
+     * NOTE: the default value must be false to keep backwards compatibility.
+     */
+    private boolean ignoreExceptionsFromRealms = false;
+
+    /**
      * Default no-argument constructor, does nothing.
      */
     public ModularRealmAuthorizer() {
@@ -170,6 +185,46 @@
         applyRolePermissionResolverToRealms();
     }
 
+    /**
+     * Returns true if Exceptions thrown by AuthorizationRealms should be ignored.
+     * If the value is false (the default) the AuthorizationExceptions will propagate back to the caller.
+     *
+     * @return
+     * @since 1.3
+     */
+    public boolean isIgnoreExceptionsFromRealms()
+    {
+        return ignoreExceptionsFromRealms;
+    }
+
+    /**
+     * Sets if the Exceptions thrown from AuthorizationRealms should be ignored.  Useful when multiple
+     * realms are configured, and one of them throws Exceptions, and you want other realms to be consulted. <BR/>
+     * If the value is false (the default) the AuthorizationExceptions will propagate back to the caller.
+     *
+     * @param ignoreExceptionsFromRealms true if Exceptions should be ignored from AuthorizationRealms.
+     * @since 1.3
+     */
+    public void setIgnoreExceptionsFromRealms( boolean ignoreExceptionsFromRealms )
+    {
+        this.ignoreExceptionsFromRealms = ignoreExceptionsFromRealms;
+    }
+
+    /**
+     * Checks if ShiroExceptions thrown from a Realm when checking authz should be ignored.<BR/>
+     * The default implementation just checks the value of isIgnoreExceptionsFromRealms().
+     * @param realm The realm from which the ShiroException was thrown.
+     * @param e The Exception that was thrown by the realm when checking authz.
+     */
+    protected void checkIgnoreExceptionsFromRealm( Realm realm, ShiroException e) {
+        if(isIgnoreExceptionsFromRealms()) {
+            log.trace( "Realm: [{}], caused an exception that will be ignored: {}", new Object[] {realm, e.getMessage(), e } );
+        }
+        else {
+            throw e;
+        }
+    }
+    
 
     /**
      * Sets the internal {@link #getRolePermissionResolver} on any internal configured
@@ -220,8 +275,13 @@
         assertRealmsConfigured();
         for (Realm realm : getRealms()) {
             if (!(realm instanceof Authorizer)) continue;
-            if (((Authorizer) realm).isPermitted(principals, permission)) {
-                return true;
+            try {
+                if (((Authorizer) realm).isPermitted(principals, permission)) {
+                    return true;
+                }
+            }
+            catch(ShiroException e) {
+                checkIgnoreExceptionsFromRealm(realm, e);
             }
         }
         return false;
@@ -236,8 +296,13 @@
         assertRealmsConfigured();
         for (Realm realm : getRealms()) {
             if (!(realm instanceof Authorizer)) continue;
-            if (((Authorizer) realm).isPermitted(principals, permission)) {
-                return true;
+            try {
+                if (((Authorizer) realm).isPermitted(principals, permission)) {
+                    return true;
+                }
+            }
+            catch(ShiroException e) {
+                checkIgnoreExceptionsFromRealm(realm, e);
             }
         }
         return false;
@@ -371,8 +436,13 @@
         assertRealmsConfigured();
         for (Realm realm : getRealms()) {
             if (!(realm instanceof Authorizer)) continue;
-            if (((Authorizer) realm).hasRole(principals, roleIdentifier)) {
-                return true;
+            try {
+                if (((Authorizer) realm).hasRole(principals, roleIdentifier)) {
+                    return true;
+                }
+            }
+            catch(ShiroException e) {
+                checkIgnoreExceptionsFromRealm(realm, e);
             }
         }
         return false;
diff --git a/core/src/test/java/org/apache/shiro/authz/ModularRealmAuthorizerTest.java b/core/src/test/java/org/apache/shiro/authz/ModularRealmAuthorizerTest.java
index edd046b..7802911 100644
--- a/core/src/test/java/org/apache/shiro/authz/ModularRealmAuthorizerTest.java
+++ b/core/src/test/java/org/apache/shiro/authz/ModularRealmAuthorizerTest.java
@@ -18,46 +18,84 @@
  */
 package org.apache.shiro.authz;
 
+import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 
-import junit.framework.Assert;
 
-import org.apache.shiro.authc.AuthenticationException;
 import org.apache.shiro.authc.AuthenticationInfo;
 import org.apache.shiro.authc.AuthenticationToken;
 import org.apache.shiro.authz.permission.RolePermissionResolver;
+import org.apache.shiro.authz.permission.WildcardPermission;
 import org.apache.shiro.realm.AuthorizingRealm;
 import org.apache.shiro.realm.Realm;
 import org.apache.shiro.subject.PrincipalCollection;
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static junit.framework.Assert.*;
+import static org.hamcrest.CoreMatchers.*;
 
 public class ModularRealmAuthorizerTest
 {
+    @Rule
+    public ExpectedException expected = ExpectedException.none();
+
+    private static String INVALID_PRIV = "priv:invalid";
+    private static String VALID_PRIV = "priv:valid";
+
+    private static String INVALID_ROLE = "invalid-role";
+    private static String VALID_ROLE = "valid-role";
+
+    private ModularRealmAuthorizer modRealmAuthz;
     
+    private Collection<Realm> realms = new ArrayList<Realm>();
+
+    private List<Permission> permissions = new ArrayList<Permission>();
+
+    private List<String> roles = new ArrayList<String>();
+
+    private PrincipalCollection principalCollection = EasyMock.createNiceMock(PrincipalCollection.class);
+    
+    @Before
+    public void setUp()
+    {
+        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(Collections.singleton(VALID_ROLE));
+        authorizationInfo.addStringPermission(VALID_PRIV);
+
+        permissions.add(new WildcardPermission(INVALID_PRIV));
+        permissions.add(new WildcardPermission(VALID_PRIV));
+
+        roles.add(INVALID_ROLE);
+        roles.add(VALID_ROLE);
+        
+        realms.add(new ExceptionThrowingAuthorizingRealm());
+        realms.add(new MockAuthorizingRealm(authorizationInfo));
+
+        modRealmAuthz = new ModularRealmAuthorizer();
+        modRealmAuthz.setRealms( realms );
+    }
+
+    /**
+     * Tests setting the RolePermissionResolver is propagated to realms.
+     */
     @Test
     public void testSettingOfRolePermissionResolver()
     {
-        Collection<Realm> realms = new ArrayList<Realm>();
-        
-        realms.add( new MockAuthorizingRealm() );
-        realms.add( new MockAuthorizingRealm() );
-        
-        // its null to start with
-        for ( Realm realm : realms )
-        {
-            Assert.assertNull( ((AuthorizingRealm)realm).getRolePermissionResolver() );
-        }
-        
-        ModularRealmAuthorizer modRealmAuthz = new ModularRealmAuthorizer();
-        modRealmAuthz.setRealms( realms );
+        // make sure initializing a realm has a null rolePermissionResolver (or the next test is not valid)
+        assertNull( new MockAuthorizingRealm().getRolePermissionResolver() );
         
         // make sure they are still null
         for ( Realm realm : realms )
         {
-            Assert.assertNull( ((AuthorizingRealm)realm).getRolePermissionResolver() );
+            assertNull( ( (AuthorizingRealm) realm ).getRolePermissionResolver() );
         }
         
         // now set the RolePermissionResolver
@@ -74,7 +112,7 @@
         for ( Realm realm : realms )
         {
             // check for same instance
-            Assert.assertTrue( ((AuthorizingRealm)realm).getRolePermissionResolver() == rolePermissionResolver );
+            assertTrue( ( (AuthorizingRealm) realm ).getRolePermissionResolver() == rolePermissionResolver );
         }
         
         // add a new realm and make sure the RolePermissionResolver is set
@@ -91,25 +129,370 @@
 //        {
 //            Assert.assertNull( ((AuthorizingRealm)realm).getRolePermissionResolver() );
 //        }
-        
-        
     }
-    
-    class MockAuthorizingRealm extends AuthorizingRealm
-    {
 
-        @Override
-        protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals )
-        {
-            return null;
+    /**
+     * @since 1.3
+     */
+    @Test
+    public void testIsPermittedWithString() {
+
+        try {
+            modRealmAuthz.isPermitted(principalCollection, VALID_PRIV);
+            fail("Expected AuthorizationException");
+        }
+        catch(AuthorizationException e){
+            // expected
+        }
+
+        // now with ignoring exceptions
+        modRealmAuthz.setIgnoreExceptionsFromRealms( true );
+
+        assertFalse( modRealmAuthz.isPermitted( principalCollection, INVALID_PRIV ) );
+        assertTrue(modRealmAuthz.isPermitted(principalCollection, VALID_PRIV));
+    }
+
+    /**
+     * @since 1.3
+     */
+    @Test
+    public void testIsPermittedWithPermission() {
+
+        try {
+            modRealmAuthz.isPermitted(principalCollection, new WildcardPermission(VALID_PRIV));
+            fail("Expected AuthorizationException");
+        }
+        catch(AuthorizationException e){
+            // expected
+        }
+
+        // now with ignoring exceptions
+        modRealmAuthz.setIgnoreExceptionsFromRealms( true );
+
+        assertFalse(modRealmAuthz.isPermitted(principalCollection, new WildcardPermission(INVALID_PRIV)));
+        assertTrue(modRealmAuthz.isPermitted(principalCollection, new WildcardPermission(VALID_PRIV)));
+    }
+
+    /**
+     * @since 1.3
+     */
+    @Test
+    public void testIsPermittedWithStrings() {
+
+        try {
+            modRealmAuthz.isPermitted(principalCollection, INVALID_PRIV, VALID_PRIV);
+            fail("Expected AuthorizationException");
+        }
+        catch(AuthorizationException e){
+            // expected
+        }
+
+        // now with ignoring exceptions
+        modRealmAuthz.setIgnoreExceptionsFromRealms( true );
+
+        assertThat( modRealmAuthz.isPermitted( principalCollection, INVALID_PRIV, VALID_PRIV ),
+                    equalTo( new boolean[]{ false, true } ) );
+        assertThat( modRealmAuthz.isPermitted( principalCollection, permissions ),
+                    equalTo( new boolean[]{ false, true } ) );
+    }
+
+    /**
+     * @since 1.3
+     */
+    @Test
+    public void testIsPermittedWithPermissions() {
+
+        try {
+            modRealmAuthz.isPermitted(principalCollection, permissions);
+            fail("Expected AuthorizationException");
+        }
+        catch(AuthorizationException e){
+            // expected
+        }
+
+        // now with ignoring exceptions
+        modRealmAuthz.setIgnoreExceptionsFromRealms( true );
+
+        assertThat(modRealmAuthz.isPermitted(principalCollection, permissions), equalTo(new boolean[]{ false, true }));
+    }
+
+    /**
+     * @since 1.3
+     */
+    @Test
+    public void testIsPermittedAllWithStrings() {
+
+        try {
+            modRealmAuthz.isPermittedAll( principalCollection, INVALID_PRIV, VALID_PRIV );
+            fail("Expected AuthorizationException");
+        }
+        catch(AuthorizationException e){
+            // expected
+        }
+
+        // now with ignoring exceptions
+        modRealmAuthz.setIgnoreExceptionsFromRealms( true );
+
+        assertFalse( modRealmAuthz.isPermittedAll( principalCollection, INVALID_PRIV, VALID_PRIV ) );
+        assertTrue( modRealmAuthz.isPermittedAll( principalCollection, VALID_PRIV, VALID_PRIV ) );
+    }
+
+    /**
+     * @since 1.3
+     */
+    @Test
+    public void testIsPermittedAllWithPermissions() {
+
+        try {
+            modRealmAuthz.isPermittedAll(principalCollection, permissions);
+            fail("Expected AuthorizationException");
+        }
+        catch(AuthorizationException e){
+            // expected
+        }
+
+        // now with ignoring exceptions
+        modRealmAuthz.setIgnoreExceptionsFromRealms( true );
+
+        assertFalse( modRealmAuthz.isPermittedAll( principalCollection, permissions ) );
+    }
+
+    /**
+     * @since 1.3
+     */
+    @Test
+    public void testHasRole() {
+
+        try {
+            modRealmAuthz.hasRole( principalCollection, VALID_ROLE );
+            fail("Expected AuthorizationException");
+        }
+        catch(AuthorizationException e){
+            // expected
+        }
+
+        // now with ignoring exceptions
+        modRealmAuthz.setIgnoreExceptionsFromRealms( true );
+
+        assertTrue(modRealmAuthz.hasRole( principalCollection, VALID_ROLE));
+        assertFalse(modRealmAuthz.hasRole( principalCollection, INVALID_ROLE ));
+    }
+
+    /**
+     * @since 1.3
+     */
+    @Test
+    public void testHasRoles() {
+
+        try {
+            modRealmAuthz.hasRoles( principalCollection, roles );
+            fail("Expected AuthorizationException");
+        }
+        catch(AuthorizationException e){
+            // expected
+        }
+
+        // now with ignoring exceptions
+        modRealmAuthz.setIgnoreExceptionsFromRealms( true );
+
+        assertThat( modRealmAuthz.hasRoles( principalCollection, roles ), equalTo( new boolean[]{ false, true } ) );
+    }
+
+    /**
+     * @since 1.3
+     */
+    @Test
+    public void testHasAllRoles() {
+
+        try {
+            modRealmAuthz.hasAllRoles( principalCollection, roles );
+            fail("Expected AuthorizationException");
+        }
+        catch(AuthorizationException e){
+            // expected
+        }
+
+        // now with ignoring exceptions
+        modRealmAuthz.setIgnoreExceptionsFromRealms( true );
+
+        assertFalse( modRealmAuthz.hasAllRoles( principalCollection, roles ) );
+    }
+
+    /**
+     * @since 1.3
+     */
+    @Test
+    public void testCheckPermissionWithString() {
+
+        try {
+            modRealmAuthz.checkPermission( principalCollection, VALID_PRIV );
+            fail("Expected AuthorizationException");
+        }
+        catch(AuthorizationException e){
+            // expected
+        }
+
+        // now with ignoring exceptions
+        modRealmAuthz.setIgnoreExceptionsFromRealms( true );
+
+        modRealmAuthz.checkPermission( principalCollection, VALID_PRIV );
+    }
+
+    /**
+     * @since 1.3
+     */
+    @Test
+    public void testCheckPermissionWithStrings() {
+
+        try {
+            modRealmAuthz.checkPermission(principalCollection, new WildcardPermission(VALID_PRIV));
+            fail("Expected AuthorizationException");
+        }
+        catch(AuthorizationException e){
+            // expected
+        }
+
+        // now with ignoring exceptions
+        modRealmAuthz.setIgnoreExceptionsFromRealms( true );
+
+        modRealmAuthz.checkPermission(principalCollection, new WildcardPermission(VALID_PRIV));
+    }
+
+    /**
+     * @since 1.3
+     */
+    @Test
+    public void testCheckPermissionsWithStrings() {
+
+        try {
+            modRealmAuthz.checkPermissions(principalCollection, VALID_PRIV );
+            fail("Expected AuthorizationException");
+        }
+        catch(AuthorizationException e){
+            // expected
+        }
+
+        // now with ignoring exceptions
+        modRealmAuthz.setIgnoreExceptionsFromRealms( true );
+
+        modRealmAuthz.checkPermissions(principalCollection, VALID_PRIV);
+    }
+
+    /**
+     * @since 1.3
+     */
+    @Test
+    public void testCheckPermissionsWithPermissions() {
+
+        Collection<Permission> validPermissions = Collections.<Permission>singleton(new WildcardPermission(VALID_PRIV));
+        try {
+            modRealmAuthz.checkPermissions(principalCollection, validPermissions);
+            fail("Expected AuthorizationException");
+        }
+        catch(AuthorizationException e){
+            // expected
+        }
+
+        // now with ignoring exceptions
+        modRealmAuthz.setIgnoreExceptionsFromRealms( true );
+
+        modRealmAuthz.checkPermissions(principalCollection, validPermissions);
+    }
+
+    /**
+     * @since 1.3
+     */
+    @Test
+    public void testCheckRole() {
+
+        try {
+            modRealmAuthz.checkRole(principalCollection, VALID_ROLE);
+            fail("Expected AuthorizationException");
+        }
+        catch(AuthorizationException e){
+            // expected
+        }
+
+        // now with ignoring exceptions
+        modRealmAuthz.setIgnoreExceptionsFromRealms( true );
+
+        modRealmAuthz.checkRole(principalCollection, VALID_ROLE);
+    }
+
+    /**
+     * @since 1.3
+     */
+    @Test
+    public void testCheckRoles() {
+
+        try {
+            modRealmAuthz.checkRoles(principalCollection, VALID_ROLE);
+            fail("Expected AuthorizationException");
+        }
+        catch(AuthorizationException e){
+            // expected
+        }
+
+        // now with ignoring exceptions
+        modRealmAuthz.setIgnoreExceptionsFromRealms( true );
+
+        modRealmAuthz.checkRoles(principalCollection, VALID_ROLE);
+    }
+
+    /**
+     * @since 1.3
+     */
+    @Test
+    public void testCheckRolesCollection() {
+
+        Collection<String> validRoles = Collections.singleton(VALID_ROLE);
+        try {
+            modRealmAuthz.checkRoles(principalCollection, validRoles);
+            fail("Expected AuthorizationException");
+        }
+        catch(AuthorizationException e){
+            // expected
+        }
+
+        // now with ignoring exceptions
+        modRealmAuthz.setIgnoreExceptionsFromRealms( true );
+
+        modRealmAuthz.checkRoles(principalCollection, validRoles);
+    }
+
+    class MockAuthorizingRealm extends AuthorizingRealm {
+
+        private final AuthorizationInfo authorizationInfo;
+
+        public MockAuthorizingRealm () {
+            this(null);
+        }
+
+        public MockAuthorizingRealm( AuthorizationInfo authorizationInfo ) {
+            this.authorizationInfo = authorizationInfo;
         }
 
         @Override
-        protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token )
-            throws AuthenticationException
-        {
+        protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals ) {
+            return authorizationInfo;
+        }
+
+        @Override
+        protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token ) {
             return null;
         }
-        
+    }
+
+    class ExceptionThrowingAuthorizingRealm extends AuthorizingRealm {
+
+        @Override
+        protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals ) {
+            throw new AuthorizationException( "Thrown by a Test Realm that only throws exceptions." );
+        }
+
+        @Override
+        protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token ) {
+            return null;
+        }
     }
 }