/** * Copyright (c) Codice Foundation * <p> * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or any later version. * <p> * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. */ package org.codice.ddf.security.idp.cache; import java.util.HashSet; import java.util.Set; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Element; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.cache.RemovalListener; import com.google.common.cache.RemovalNotification; public class CookieCache { private static final Logger LOGGER = LoggerFactory.getLogger(CookieCache.class); private static final int DEFAULT_EXPIRATION_MINUTES = 30; private int currentExpiration = DEFAULT_EXPIRATION_MINUTES; private Cache<String, DataWrapper> cache = CacheBuilder.newBuilder() .expireAfterWrite(DEFAULT_EXPIRATION_MINUTES, TimeUnit.MINUTES) .removalListener(new RemovalListenerLogger()) .build(); public void clearCache() { cache.invalidateAll(); } /** * Puts the SAML assertion into the cache * * @param key key corresponding to the SAML assertion * @param token the SAML assertion to be cached */ public void cacheSamlAssertion(String key, Element token) { cache.put(key, new DataWrapper(token)); } public void addActiveSp(String key, String activeSp) { DataWrapper dataWrapper = cache.getIfPresent(key); if (dataWrapper == null) { LOGGER.warn("Cannot add sp [{}] for [{}]: does not exist", activeSp, key); return; } synchronized (dataWrapper) { dataWrapper.activeSpSet.add(activeSp); } } /** * Retrieves the SAML assertion associated with the provided key. * * @param key the corresponding key for the assertion * @return the SecurityToken object associated with the reference, or null */ public Element getSamlAssertion(String key) { DataWrapper dataWrapper = cache.getIfPresent(key); if (dataWrapper != null) { synchronized (dataWrapper) { return dataWrapper.element; } } return null; } public void removeSamlAssertion(String key) { DataWrapper dataWrapper = cache.getIfPresent(key); if (dataWrapper != null) { LOGGER.debug("Expiring Saml assertion due to LogoutRequest\n[{}:{}]", key, dataWrapper.element); synchronized (dataWrapper) { dataWrapper.element = null; } } } public Set<String> getActiveSpSet(String key) { DataWrapper dataWrapper = cache.getIfPresent(key); if (dataWrapper != null) { synchronized (dataWrapper) { return new HashSet<>(dataWrapper.activeSpSet); } } return new HashSet<>(); } /** * Set the expiration time for the cache. <br/><br/> * Note: This will also reset the expiration times of any items currently in the cache. * * @param expirationMinutes Value (in minutes) to set the cache expiration to. */ public synchronized void setExpirationTime(int expirationMinutes) { if (expirationMinutes != currentExpiration) { LOGGER.debug( "New expiration value passed in. Changing cache to expire every {} minutes instead of every {}.", expirationMinutes, currentExpiration); Cache<String, DataWrapper> tmpCache = CacheBuilder.newBuilder() .expireAfterWrite(expirationMinutes, TimeUnit.MINUTES) .removalListener(new RemovalListenerLogger()) .build(); tmpCache.putAll(cache.asMap()); LOGGER.debug("All cache items updated to expire after {} minutes.", expirationMinutes); cache = tmpCache; currentExpiration = expirationMinutes; } else { LOGGER.debug("Incoming time of {} matches current expiration time. Not updating cache.", expirationMinutes); } } /** * Listens for removal notifications from the cache and logs each time a removal is performed. */ private static class RemovalListenerLogger implements RemovalListener<String, DataWrapper> { @Override public void onRemoval(RemovalNotification<String, DataWrapper> notification) { LOGGER.debug("Expiring SAML ref:assertion {}:{} due to {}.", notification.getKey(), notification.getValue(), notification.getCause() .toString()); } } /** * Data access to each of the class variables should be synchronized */ private static class DataWrapper { private Element element; private final Set<String> activeSpSet; private DataWrapper(Element element) { this.element = element; this.activeSpSet = new HashSet<>(); } } }