killbill-aplcache

server: stop checking realms after first success See upstream

11/10/2015 5:51:18 PM

Details

diff --git a/profiles/killbill/src/main/java/org/apache/shiro/authc/pam/ModularRealmAuthenticatorWith540.java b/profiles/killbill/src/main/java/org/apache/shiro/authc/pam/ModularRealmAuthenticatorWith540.java
new file mode 100644
index 0000000..14c5eda
--- /dev/null
+++ b/profiles/killbill/src/main/java/org/apache/shiro/authc/pam/ModularRealmAuthenticatorWith540.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2015 Groupon, Inc
+ * Copyright 2015 The Billing Project, LLC
+ *
+ * The Billing Project 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.authc.pam;
+
+import java.util.Collection;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.realm.Realm;
+import org.killbill.billing.server.security.FirstSuccessfulStrategyWith540;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+// See https://issues.apache.org/jira/browse/SHIRO-540
+public class ModularRealmAuthenticatorWith540 extends ModularRealmAuthenticator {
+
+    private static final Logger log = LoggerFactory.getLogger(ModularRealmAuthenticator.class);
+
+    public ModularRealmAuthenticatorWith540(final ModularRealmAuthenticator delegate) {
+        setRealms(delegate.getRealms());
+        setAuthenticationStrategy(delegate.getAuthenticationStrategy());
+    }
+
+    /**
+     * Performs the multi-realm authentication attempt by calling back to a {@link AuthenticationStrategy} object
+     * as each realm is consulted for {@code AuthenticationInfo} for the specified {@code token}.
+     *
+     * @param realms the multiple realms configured on this Authenticator instance.
+     * @param token  the submitted AuthenticationToken representing the subject's (user's) log-in principals and credentials.
+     * @return an aggregated AuthenticationInfo instance representing account data across all the successfully
+     * consulted realms.
+     */
+    protected AuthenticationInfo doMultiRealmAuthentication(final Collection<Realm> realms, final AuthenticationToken token) {
+
+        final AuthenticationStrategy strategy = getAuthenticationStrategy();
+
+        AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
+
+        if (log.isTraceEnabled()) {
+            log.trace("Iterating through {} realms for PAM authentication", realms.size());
+        }
+
+        for (final Realm realm : realms) {
+
+            aggregate = strategy.beforeAttempt(realm, token, aggregate);
+
+            if (realm.supports(token)) {
+
+                log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);
+
+                AuthenticationInfo info = null;
+                Throwable t = null;
+                try {
+                    info = realm.getAuthenticationInfo(token);
+                } catch (final Throwable throwable) {
+                    t = throwable;
+                    if (log.isDebugEnabled()) {
+                        final String msg = "Realm [" + realm + "] threw an exception during a multi-realm authentication attempt:";
+                        log.debug(msg, t);
+                    }
+                }
+
+                aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
+
+                if (strategy instanceof FirstSuccessfulStrategyWith540) {
+                    // check if we should check the next realm, or just stop here.
+                    if (!((FirstSuccessfulStrategyWith540) strategy).continueAfterAttempt(info, aggregate, t)) {
+                        log.trace("Will not consult any other realms for authentication, last realm [{}].", realm);
+                        break;
+                    }
+                }
+
+            } else {
+                log.debug("Realm [{}] does not support token {}.  Skipping realm.", realm, token);
+            }
+        }
+
+        aggregate = strategy.afterAllAttempts(token, aggregate);
+
+        return aggregate;
+    }
+}
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
index 96cd7ef..84f15f0 100644
--- a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillShiroWebModule.java
@@ -23,13 +23,18 @@ import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 
+import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
+import org.apache.shiro.authc.pam.ModularRealmAuthenticatorWith540;
 import org.apache.shiro.cache.CacheManager;
 import org.apache.shiro.guice.web.ShiroWebModuleWith435;
 import org.apache.shiro.session.mgt.SessionManager;
 import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
+import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
+import org.apache.shiro.web.mgt.WebSecurityManager;
 import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
 import org.apache.shiro.web.util.WebUtils;
 import org.killbill.billing.jaxrs.resources.JaxrsResource;
+import org.killbill.billing.server.security.FirstSuccessfulStrategyWith540;
 import org.killbill.billing.util.config.RbacConfig;
 import org.killbill.billing.util.glue.EhCacheManagerProvider;
 import org.killbill.billing.util.glue.IniRealmProvider;
@@ -42,7 +47,13 @@ import org.skife.config.ConfigSource;
 import org.skife.config.ConfigurationObjectFactory;
 
 import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
 import com.google.inject.binder.AnnotatedBindingBuilder;
+import com.google.inject.matcher.AbstractMatcher;
+import com.google.inject.matcher.Matchers;
+import com.google.inject.spi.InjectionListener;
+import com.google.inject.spi.TypeEncounter;
+import com.google.inject.spi.TypeListener;
 
 // For Kill Bill server only.
 // See org.killbill.billing.util.glue.KillBillShiroModule for Kill Bill library.
@@ -74,6 +85,29 @@ public class KillBillShiroWebModule extends ShiroWebModuleWith435 {
         if (KillBillShiroModule.isRBACEnabled()) {
             addFilterChain(JaxrsResource.PREFIX + "/**", Key.get(CorsBasicHttpAuthenticationFilter.class));
         }
+
+        bindListener(new AbstractMatcher<TypeLiteral<?>>() {
+                         @Override
+                         public boolean matches(final TypeLiteral<?> o) {
+                             return Matchers.subclassesOf(WebSecurityManager.class).matches(o.getRawType());
+                         }
+                     },
+                     new TypeListener() {
+                         @Override
+                         public <I> void hear(final TypeLiteral<I> typeLiteral, final TypeEncounter<I> typeEncounter) {
+                             typeEncounter.register(new InjectionListener<I>() {
+                                 @Override
+                                 public void afterInjection(final Object o) {
+                                     final DefaultWebSecurityManager webSecurityManager = (DefaultWebSecurityManager) o;
+                                     if (webSecurityManager.getAuthenticator() instanceof ModularRealmAuthenticator) {
+                                         final ModularRealmAuthenticator authenticator = (ModularRealmAuthenticator) webSecurityManager.getAuthenticator();
+                                         authenticator.setAuthenticationStrategy(new FirstSuccessfulStrategyWith540());
+                                         webSecurityManager.setAuthenticator(new ModularRealmAuthenticatorWith540(authenticator));
+                                     }
+                                 }
+                             });
+                         }
+                     });
     }
 
     @Override
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/security/FirstSuccessfulStrategyWith540.java b/profiles/killbill/src/main/java/org/killbill/billing/server/security/FirstSuccessfulStrategyWith540.java
new file mode 100644
index 0000000..cd39ac8
--- /dev/null
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/security/FirstSuccessfulStrategyWith540.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2015 Groupon, Inc
+ * Copyright 2015 The Billing Project, LLC
+ *
+ * The Billing Project 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.killbill.billing.server.security;
+
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.pam.FirstSuccessfulStrategy;
+
+public class FirstSuccessfulStrategyWith540 extends FirstSuccessfulStrategy {
+
+    public boolean continueAfterAttempt(final AuthenticationInfo singleRealmInfo, final AuthenticationInfo aggregateInfo, final Throwable t) {
+        return !(aggregateInfo != null && aggregateInfo == singleRealmInfo);
+    }
+}