killbill-uncached

Details

pom.xml 2(+1 -1)

diff --git a/pom.xml b/pom.xml
index 02d1b7e..35d1373 100644
--- a/pom.xml
+++ b/pom.xml
@@ -21,7 +21,7 @@
     <parent>
         <artifactId>killbill-oss-parent</artifactId>
         <groupId>org.kill-bill.billing</groupId>
-        <version>0.142.6</version>
+        <version>0.142.7</version>
     </parent>
     <artifactId>killbill</artifactId>
     <version>0.20.3-SNAPSHOT</version>
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillSessionStorageEvaluator.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillSessionStorageEvaluator.java
new file mode 100644
index 0000000..cce4626
--- /dev/null
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillSessionStorageEvaluator.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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.modules;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.shiro.mgt.SessionStorageEvaluator;
+import org.apache.shiro.subject.Subject;
+import org.apache.shiro.web.util.RequestPairSource;
+import org.killbill.billing.jaxrs.resources.JaxrsResource;
+
+public class KillBillSessionStorageEvaluator implements SessionStorageEvaluator {
+
+    @Override
+    public boolean isSessionStorageEnabled(final Subject subject) {
+        if (subject.getSession(false) != null) {
+            // Use what already exists
+            return true;
+        }
+
+        return isSessionCreationEnabled(subject);
+    }
+
+    private boolean isSessionCreationEnabled(final Subject requestPairSource) {
+        if (requestPairSource instanceof RequestPairSource) {
+            final RequestPairSource source = (RequestPairSource) requestPairSource;
+            return isSessionCreationEnabled(source.getServletRequest());
+        }
+        return false; // By default
+    }
+
+    private boolean isSessionCreationEnabled(final ServletRequest request) {
+        if (request != null) {
+            // Only create new sessions via the /1.0/kb/security/permissions endpoint, as this is what is used today
+            // by Kaui to initiate the session.
+            // If we have another use-case one day, we could think about introducing a proper 'login' endpoint...
+            return isPermissionsLookupCall(request);
+        }
+        return false; // By default
+    }
+
+    private boolean isPermissionsLookupCall(final ServletRequest request) {
+        if (request instanceof HttpServletRequest) {
+            final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+            final String path = httpServletRequest.getPathInfo();
+            return (JaxrsResource.SECURITY_PATH + "/permissions").equals(path);
+        }
+        return false;
+    }
+}
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 90b2144..b8a7992 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
@@ -31,8 +31,8 @@ import org.apache.shiro.authc.pam.ModularRealmAuthenticatorWith540;
 import org.apache.shiro.authz.ModularRealmAuthorizer;
 import org.apache.shiro.cache.CacheManager;
 import org.apache.shiro.guice.web.ShiroWebModuleWith435;
+import org.apache.shiro.mgt.SubjectDAO;
 import org.apache.shiro.realm.Realm;
-import org.apache.shiro.session.mgt.DefaultSessionManager;
 import org.apache.shiro.session.mgt.SessionManager;
 import org.apache.shiro.session.mgt.eis.SessionDAO;
 import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
@@ -41,6 +41,7 @@ import org.apache.shiro.web.mgt.WebSecurityManager;
 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.server.security.KillBillWebSessionManager;
 import org.killbill.billing.server.security.KillbillJdbcTenantRealm;
 import org.killbill.billing.util.config.definition.RbacConfig;
 import org.killbill.billing.util.config.definition.RedisCacheConfig;
@@ -118,6 +119,7 @@ public class KillBillShiroWebModule extends ShiroWebModuleWith435 {
 
         if (KillBillShiroModule.isRBACEnabled()) {
             addFilterChain(JaxrsResource.PREFIX + "/**", Key.get(CorsBasicHttpAuthenticationFilter.class));
+            addFilterChain(JaxrsResource.PLUGINS_PATH + "/**", Key.get(CorsBasicHttpAuthenticationOptionalFilter.class));
         }
     }
 
@@ -131,13 +133,15 @@ public class KillBillShiroWebModule extends ShiroWebModuleWith435 {
     protected void bindSessionManager(final AnnotatedBindingBuilder<SessionManager> bind) {
         // Bypass the servlet container completely for session management and delegate it to Shiro.
         // The default session timeout is 30 minutes.
-        bind.to(DefaultSessionManager.class).asEagerSingleton();
+        bind.to(KillBillWebSessionManager.class).asEagerSingleton();
+
+        bind(SubjectDAO.class).toProvider(KillBillWebSubjectDAOProvider.class).asEagerSingleton();
 
         // Magic provider to configure the session DAO
         bind(SessionDAO.class).toProvider(SessionDAOProvider.class).asEagerSingleton();
     }
 
-    public static final class CorsBasicHttpAuthenticationFilter extends BasicHttpAuthenticationFilter {
+    public static class CorsBasicHttpAuthenticationFilter extends BasicHttpAuthenticationFilter {
 
         @Override
         protected boolean isAccessAllowed(final ServletRequest request, final ServletResponse response, final Object mappedValue) {
@@ -149,6 +153,19 @@ public class KillBillShiroWebModule extends ShiroWebModuleWith435 {
         }
     }
 
+    public static final class CorsBasicHttpAuthenticationOptionalFilter extends CorsBasicHttpAuthenticationFilter {
+
+        protected boolean onAccessDenied(final ServletRequest request, final ServletResponse response) throws Exception {
+            if (isLoginAttempt(request, response)) {
+                // Attempt to log-in
+                executeLogin(request, response);
+            }
+
+            // Unlike the original method, we don't send a challenge on failure but simply allow the request to continue
+            return true;
+        }
+    }
+
     private final class DefaultWebSecurityManagerTypeListener implements TypeListener {
 
         @Override
diff --git a/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillWebSubjectDAOProvider.java b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillWebSubjectDAOProvider.java
new file mode 100644
index 0000000..f6a986e
--- /dev/null
+++ b/profiles/killbill/src/main/java/org/killbill/billing/server/modules/KillBillWebSubjectDAOProvider.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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.modules;
+
+import org.apache.shiro.mgt.SubjectDAO;
+import org.killbill.billing.util.glue.KillBillSubjectDAO;
+
+import com.google.inject.Provider;
+
+public class KillBillWebSubjectDAOProvider implements Provider<SubjectDAO> {
+
+    @Override
+    public SubjectDAO get() {
+        final KillBillSubjectDAO killBillSubjectDAO = new KillBillSubjectDAO();
+        killBillSubjectDAO.setSessionStorageEvaluator(new KillBillSessionStorageEvaluator());
+        return killBillSubjectDAO;
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/glue/EhcacheShiroManagerProvider.java b/util/src/main/java/org/killbill/billing/util/glue/EhcacheShiroManagerProvider.java
index 72f355a..0d83057 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/EhcacheShiroManagerProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/EhcacheShiroManagerProvider.java
@@ -28,6 +28,7 @@ import org.apache.shiro.cache.Cache;
 import org.apache.shiro.cache.CacheException;
 import org.apache.shiro.mgt.DefaultSecurityManager;
 import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.mgt.SubjectDAO;
 import org.ehcache.integrations.shiro.EhcacheShiro;
 import org.ehcache.integrations.shiro.EhcacheShiroManager;
 import org.killbill.billing.util.config.definition.EhCacheConfig;
@@ -39,16 +40,19 @@ import com.codahale.metrics.MetricRegistry;
 public class EhcacheShiroManagerProvider extends EhCacheProviderBase implements Provider<EhcacheShiroManager> {
 
     private final SecurityManager securityManager;
+    private final SubjectDAO subjectDAO;
     private final CacheManager eh107CacheManager;
     private final org.ehcache.CacheManager ehcacheCacheManager;
 
     @Inject
     public EhcacheShiroManagerProvider(final SecurityManager securityManager,
+                                       final SubjectDAO subjectDAO,
                                        final CacheManager eh107CacheManager,
                                        final MetricRegistry metricRegistry,
                                        final EhCacheConfig cacheConfig) {
         super(metricRegistry, cacheConfig);
         this.securityManager = securityManager;
+        this.subjectDAO = subjectDAO;
         this.eh107CacheManager = eh107CacheManager;
         this.ehcacheCacheManager = getEhcacheManager();
     }
@@ -63,7 +67,7 @@ public class EhcacheShiroManagerProvider extends EhCacheProviderBase implements 
             // For RBAC only (see also KillbillJdbcTenantRealmProvider)
             final DefaultSecurityManager securityManager = (DefaultSecurityManager) this.securityManager;
             securityManager.setCacheManager(shiroEhCacheManager);
-            securityManager.setSubjectDAO(new KillBillSubjectDAO());
+            securityManager.setSubjectDAO(subjectDAO);
         }
 
         return shiroEhCacheManager;
diff --git a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
index 420a565..2f9a3e0 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/KillBillShiroModule.java
@@ -21,6 +21,7 @@ package org.killbill.billing.util.glue;
 import org.apache.shiro.cache.CacheManager;
 import org.apache.shiro.guice.ShiroModule;
 import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.mgt.SubjectDAO;
 import org.apache.shiro.realm.text.IniRealm;
 import org.apache.shiro.session.mgt.DefaultSessionManager;
 import org.apache.shiro.session.mgt.SessionManager;
@@ -132,6 +133,8 @@ public class KillBillShiroModule extends ShiroModule {
     protected void bindSessionManager(final AnnotatedBindingBuilder<SessionManager> bind) {
         bind.to(DefaultSessionManager.class).asEagerSingleton();
 
+        bind(SubjectDAO.class).toProvider(KillBillSubjectDAOProvider.class).asEagerSingleton();
+
         // Magic provider to configure the session DAO
         bind(SessionDAO.class).toProvider(SessionDAOProvider.class).asEagerSingleton();
     }
diff --git a/util/src/main/java/org/killbill/billing/util/glue/KillBillSubjectDAOProvider.java b/util/src/main/java/org/killbill/billing/util/glue/KillBillSubjectDAOProvider.java
new file mode 100644
index 0000000..7efa569
--- /dev/null
+++ b/util/src/main/java/org/killbill/billing/util/glue/KillBillSubjectDAOProvider.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014-2018 Groupon, Inc
+ * Copyright 2014-2018 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.util.glue;
+
+import org.apache.shiro.mgt.SessionStorageEvaluator;
+import org.apache.shiro.mgt.SubjectDAO;
+import org.apache.shiro.subject.Subject;
+
+import com.google.inject.Provider;
+
+// See org.killbill.billing.server.modules.KillBillWebSubjectDAOProvider for the web version
+public class KillBillSubjectDAOProvider implements Provider<SubjectDAO> {
+
+    @Override
+    public SubjectDAO get() {
+        final KillBillSubjectDAO killBillSubjectDAO = new KillBillSubjectDAO();
+        killBillSubjectDAO.setSessionStorageEvaluator(new SessionStorageEvaluator() {
+            @Override
+            public boolean isSessionStorageEnabled(final Subject subject) {
+                // Use what already exists
+                return subject.getSession(false) != null;
+            }
+        });
+        return killBillSubjectDAO;
+    }
+}
diff --git a/util/src/main/java/org/killbill/billing/util/glue/RedisShiroManagerProvider.java b/util/src/main/java/org/killbill/billing/util/glue/RedisShiroManagerProvider.java
index bcfa133..e992feb 100644
--- a/util/src/main/java/org/killbill/billing/util/glue/RedisShiroManagerProvider.java
+++ b/util/src/main/java/org/killbill/billing/util/glue/RedisShiroManagerProvider.java
@@ -23,6 +23,7 @@ import javax.inject.Provider;
 
 import org.apache.shiro.mgt.DefaultSecurityManager;
 import org.apache.shiro.mgt.SecurityManager;
+import org.apache.shiro.mgt.SubjectDAO;
 import org.redisson.api.RedissonClient;
 
 import com.codahale.metrics.MetricRegistry;
@@ -32,17 +33,20 @@ import static org.killbill.billing.util.glue.CacheModule.REDIS_CACHE_CLIENT;
 
 public class RedisShiroManagerProvider implements Provider<RedisShiroManager> {
 
-    private final CacheManager eh107CacheManager;
     private final SecurityManager securityManager;
+    private final SubjectDAO subjectDAO;
+    private final CacheManager eh107CacheManager;
     private final MetricRegistry metricRegistry;
     private final RedissonClient redissonClient;
 
     @Inject
     public RedisShiroManagerProvider(final SecurityManager securityManager,
+                                     final SubjectDAO subjectDAO,
                                      final CacheManager eh107CacheManager,
                                      final MetricRegistry metricRegistry,
                                      @Named(REDIS_CACHE_CLIENT) final RedissonClient redissonClient) {
         this.securityManager = securityManager;
+        this.subjectDAO = subjectDAO;
         this.eh107CacheManager = eh107CacheManager;
         this.metricRegistry = metricRegistry;
         this.redissonClient = redissonClient;
@@ -57,7 +61,7 @@ public class RedisShiroManagerProvider implements Provider<RedisShiroManager> {
             // For RBAC only (see also KillbillJdbcTenantRealmProvider)
             final DefaultSecurityManager securityManager = (DefaultSecurityManager) this.securityManager;
             securityManager.setCacheManager(shiroRedisManager);
-            securityManager.setSubjectDAO(new KillBillSubjectDAO());
+            securityManager.setSubjectDAO(subjectDAO);
         }
 
         return shiroRedisManager;
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.java
index 8bd91cc..6e455b8 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionDao.java
@@ -20,6 +20,9 @@ package org.killbill.billing.util.security.shiro.dao;
 
 import java.io.IOException;
 import java.io.Serializable;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
@@ -90,12 +93,20 @@ public class JDBCSessionDao extends CachingSessionDAO {
             return null;
         }
 
-        try {
-            return sessionModelDao.toSimpleSession();
-        } catch (final IOException e) {
-            log.warn("Corrupted cookie", e);
-            return null;
+        return toSession(sessionModelDao);
+    }
+
+    @Override
+    public Collection<Session> getActiveSessions() {
+        final Collection<Session> cachedActiveSessions = super.getActiveSessions();
+        // To make sure the ValidatingSessionManager purges old sessions on disk
+        final List<SessionModelDao> oldActiveSessionsOnDisk = dbRouter.onDemand(true).findOldActiveSessions();
+
+        final Collection<Session> activeSessions = new LinkedList<Session>(cachedActiveSessions);
+        for (final SessionModelDao sessionModelDao : oldActiveSessionsOnDisk) {
+            activeSessions.add(toSession(sessionModelDao));
         }
+        return activeSessions;
     }
 
     public void disableUpdatesForSession(final Session session) {
@@ -110,4 +121,13 @@ public class JDBCSessionDao extends CachingSessionDAO {
     private boolean shouldUpdateSession(final Session session) {
         return noUpdateSessionsCache.getIfPresent(session.getId()) == Boolean.TRUE ? Boolean.FALSE : Boolean.TRUE;
     }
+
+    private Session toSession(final SessionModelDao sessionModelDao) {
+        try {
+            return sessionModelDao.toSimpleSession();
+        } catch (final IOException e) {
+            log.warn("Corrupted cookie", e);
+            return null;
+        }
+    }
 }
diff --git a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.java b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.java
index 409f2a3..d78e6c1 100644
--- a/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.java
+++ b/util/src/main/java/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.java
@@ -18,8 +18,10 @@
 
 package org.killbill.billing.util.security.shiro.dao;
 
-import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
+import java.util.List;
+
 import org.killbill.commons.jdbi.binder.SmartBindBean;
+import org.killbill.commons.jdbi.template.KillBillSqlDaoStringTemplate;
 import org.skife.jdbi.v2.sqlobject.Bind;
 import org.skife.jdbi.v2.sqlobject.SqlQuery;
 import org.skife.jdbi.v2.sqlobject.SqlUpdate;
@@ -40,4 +42,6 @@ public interface JDBCSessionSqlDao extends Transactional<JDBCSessionSqlDao> {
     @SqlUpdate
     public void delete(@SmartBindBean final SessionModelDao sessionModelDao);
 
+    @SqlQuery
+    public List<SessionModelDao> findOldActiveSessions();
 }
diff --git a/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.sql.stg b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.sql.stg
index e6091e5..c479317 100644
--- a/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.sql.stg
+++ b/util/src/main/resources/org/killbill/billing/util/security/shiro/dao/JDBCSessionSqlDao.sql.stg
@@ -45,4 +45,19 @@ delete() ::= <<
 delete from sessions
 where id = :id
 ;
->>
\ No newline at end of file
+>>
+
+findOldActiveSessions() ::= <<
+select
+  record_id
+, id
+, start_timestamp
+, last_access_time
+, timeout
+, host
+, session_data
+from sessions
+order by record_id asc
+limit 100
+;
+>>