Started to sketch cache functionality.
authorwassa <wassa@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Tue, 26 Sep 2006 19:49:17 +0000 (19:49 +0000)
committerwassa <wassa@ab3bd59b-922f-494d-bb5f-6f0a3c29deca>
Tue, 26 Sep 2006 19:49:17 +0000 (19:49 +0000)
git-svn-id: https://subversion.switch.ch/svn/shibboleth/java-idp/trunk@2051 ab3bd59b-922f-494d-bb5f-6f0a3c29deca

src/edu/internet2/middleware/shibboleth/common/Cache.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/common/provider/BaseCache.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/common/provider/CookieCache.java [new file with mode: 0644]
src/edu/internet2/middleware/shibboleth/common/provider/ServletSessionCache.java [new file with mode: 0644]

diff --git a/src/edu/internet2/middleware/shibboleth/common/Cache.java b/src/edu/internet2/middleware/shibboleth/common/Cache.java
new file mode 100644 (file)
index 0000000..ea50b29
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
+ *
+ * Licensed 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 edu.internet2.middleware.shibboleth.common;
+
+/**
+ * @author Walter Hoehn
+ */
+public interface Cache {
+
+       public enum CacheType {
+               CLIENT_SIDE, SERVER_SIDE, CLIENT_SERVER_SHARED
+       }
+
+       public String getName();
+
+       public CacheType getCacheType();
+
+       public Object retrieve(String key);
+
+       public boolean contains(String key);
+
+       public void store(String key, String value, long duration);
+}
diff --git a/src/edu/internet2/middleware/shibboleth/common/provider/BaseCache.java b/src/edu/internet2/middleware/shibboleth/common/provider/BaseCache.java
new file mode 100644 (file)
index 0000000..da99df0
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
+ *
+ * Licensed 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 edu.internet2.middleware.shibboleth.common.provider;
+
+import java.util.Date;
+
+import edu.internet2.middleware.shibboleth.common.Cache;
+
+/**
+ * Functionality common to all implementations of <code>Cache</code>.
+ * 
+ * @author Walter Hoehn
+ */
+public abstract class BaseCache implements Cache {
+
+       private String name;
+       private CacheType cacheType;
+
+       protected BaseCache(String name, CacheType type) {
+
+               this.name = name;
+               this.cacheType = type;
+       }
+
+       public CacheType getCacheType() {
+
+               return cacheType;
+       }
+
+       public String getName() {
+
+               return name;
+       }
+
+       protected class CacheEntry {
+
+               protected Date expiration;
+               protected String value;
+
+               protected CacheEntry(String value, long duration) {
+
+                       this.value = value;
+                       expiration = new Date(System.currentTimeMillis() + (duration * 1000));
+               }
+       }
+}
diff --git a/src/edu/internet2/middleware/shibboleth/common/provider/CookieCache.java b/src/edu/internet2/middleware/shibboleth/common/provider/CookieCache.java
new file mode 100644 (file)
index 0000000..0269211
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+ * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
+ *
+ * Licensed 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 edu.internet2.middleware.shibboleth.common.provider;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.zip.GZIPOutputStream;
+
+import javax.crypto.Cipher;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import edu.internet2.middleware.shibboleth.common.Cache;
+import edu.internet2.middleware.shibboleth.utils.Base32;
+
+/**
+ * <code>Cache</code> implementation that uses browser cookies to store data. Symmetric and HMAC algorithms are used
+ * to encrypt and verify the data. Due to the size limitations of cookie storage, data may interleaved among multiple
+ * cookies.
+ * 
+ * @author Walter Hoehn
+ */
+public class CookieCache extends BaseCache implements Cache {
+
+       // TODO domain limit?
+       private HttpServletResponse response;
+       private List<Cookie> myCurrentCookies = new ArrayList<Cookie>();
+       private Map<String, CacheEntry> dataCache = new HashMap<String, CacheEntry>();
+       private static final int CHUNK_SIZE = 4 * 1024; // in KB, minimal browser requirement
+       private static final int COOKIE_LIMIT = 20; // minimal browser requirement
+       private static final String NAME_PREFIX = "IDP_CACHE:";
+       protected SecretKey secret;
+       private static SecureRandom random = new SecureRandom();
+       private String cipherAlgorithm = "DESede/CBC/PKCS5Padding";
+       private String macAlgorithm = "HmacSHA1";
+       private String storeType = "JCEKS";
+
+       CookieCache(String name, HttpServletRequest request, HttpServletResponse response) {
+
+               super(name, Cache.CacheType.CLIENT_SIDE);
+               this.response = response;
+               Cookie[] requestCookies = request.getCookies();
+               for (int i = 0; i < requestCookies.length; i++) {
+                       if (requestCookies[i].getName().startsWith(NAME_PREFIX)) {
+                               myCurrentCookies.add(requestCookies[i]);
+                       }
+               }
+
+               // TODO dechunk, decrypt, and pull in dataCache
+       }
+
+       public boolean contains(String key) {
+
+               CacheEntry entry = dataCache.get(key);
+
+               if (entry == null) { return false; }
+
+               // Clean cache if it is expired
+               if (new Date().after(((CacheEntry) entry).expiration)) {
+                       deleteFromCache(key);
+                       return false;
+               }
+
+               // OK, we have it
+               return true;
+       }
+
+       private void deleteFromCache(String key) {
+
+               dataCache.remove(key);
+               flushCache();
+       }
+
+       public Object retrieve(String key) {
+
+               CacheEntry entry = dataCache.get(key);
+
+               if (entry == null) { return null; }
+
+               // Clean cache if it is expired
+               if (new Date().after(((CacheEntry) entry).expiration)) {
+                       deleteFromCache(key);
+                       return null;
+               }
+
+               // OK, we have it
+               return entry.value;
+       }
+
+       public void store(String key, String value, long duration) {
+
+               dataCache.put(key, new CacheEntry(value, duration));
+               flushCache();
+       }
+
+       /**
+        * Secures, encodes, and writes out (to cookies) cached data.
+        */
+       private void flushCache() {
+
+               // TODO create String representation of all cache data
+               String stringData = null;
+
+               try {
+
+                       // Setup a gzipped data stream
+                       ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+                       GZIPOutputStream compressedStream = new GZIPOutputStream(byteStream);
+                       DataOutputStream dataStream = new DataOutputStream(compressedStream);
+
+                       // Write data and HMAC to stream
+                       Mac mac = Mac.getInstance(macAlgorithm);
+                       mac.init(secret);
+                       dataStream.write(mac.doFinal(stringData.getBytes()));
+                       dataStream.writeUTF(stringData);
+
+                       // Flush
+                       dataStream.flush();
+                       compressedStream.flush();
+                       compressedStream.finish();
+                       byteStream.flush();
+
+                       // Setup encryption lib
+                       Cipher cipher = Cipher.getInstance(cipherAlgorithm);
+                       byte[] iv = new byte[cipher.getBlockSize()];
+                       random.nextBytes(iv);
+                       IvParameterSpec ivSpec = new IvParameterSpec(iv);
+                       cipher.init(Cipher.ENCRYPT_MODE, secret, ivSpec);
+
+                       // Creat byte array of IV and encrypted cache
+                       byte[] encryptedData = cipher.doFinal(byteStream.toByteArray());
+                       byte[] cacheBytes = new byte[iv.length + encryptedData.length];
+                       // Write IV
+                       System.arraycopy(iv, 0, cacheBytes, 0, iv.length);
+                       // Write encrypted cache
+                       System.arraycopy(encryptedData, 0, cacheBytes, iv.length, encryptedData.length);
+
+                       // Base32 encode
+                       String encodedData = Base32.encode(cacheBytes);
+
+                       // Put into cookies
+                       interleaveInCookies(encodedData);
+
+               } catch (KeyException e) {
+                       // TODO handle
+               } catch (GeneralSecurityException e) {
+                       // TODO handle
+               } catch (IOException e) {
+                       // TODO handle
+               }
+       }
+
+       /**
+        * Writes encoded data across multiple cookies
+        */
+       private void interleaveInCookies(String data) {
+
+               // Convert the String data to a list of cookies
+               List<Cookie> cookiesToResponse = new ArrayList<Cookie>();
+               StringBuffer bufferredData = new StringBuffer(data);
+               while (bufferredData != null && bufferredData.length() > 0) {
+                       Cookie cookie = null;
+                       String name = null;
+                       if (bufferredData.length() <= getCookieSpace()) {
+                               cookie = new Cookie(name, bufferredData.toString());
+                               bufferredData = null;
+                       } else {
+                               cookie = new Cookie(name, bufferredData.substring(0, getCookieSpace() - 1));
+                               bufferredData.delete(0, getCookieSpace() - 1);
+                       }
+                       cookiesToResponse.add(cookie);
+               }
+
+               // We have to null out cookies that are no longer needed
+               for (Cookie previousCookie : myCurrentCookies) {
+                       if (!cookiesToResponse.contains(previousCookie)) {
+                               cookiesToResponse.add(new Cookie(previousCookie.getName(), null));
+                       }
+               }
+
+               // Write our cookies to the response object
+               for (Cookie cookie : cookiesToResponse) {
+                       response.addCookie(cookie);
+               }
+
+               // Update our cached copy of the cookies
+               myCurrentCookies = cookiesToResponse;
+       }
+
+       /**
+        * Returns the amount of value space available in cookies we create
+        */
+       private int getCookieSpace() {
+
+               // TODO this needs to be better
+               return 3000;
+       }
+
+}
diff --git a/src/edu/internet2/middleware/shibboleth/common/provider/ServletSessionCache.java b/src/edu/internet2/middleware/shibboleth/common/provider/ServletSessionCache.java
new file mode 100644 (file)
index 0000000..79cd5c0
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
+ *
+ * Licensed 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 edu.internet2.middleware.shibboleth.common.provider;
+
+import java.util.Date;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+import edu.internet2.middleware.shibboleth.common.Cache;
+
+/**
+ * <code>Cache</code> implementation that uses Servlet API sessions to cache data. This implementation will reap
+ * expired entries as they are accessed, but primarily relies on the servlet container and the web browser to handle
+ * invalidation of cached values.
+ * 
+ * @author Walter Hoehn
+ */
+public class ServletSessionCache extends BaseCache implements Cache {
+
+       HttpSession session;
+
+       ServletSessionCache(String name, HttpServletRequest request) {
+
+               super(name, Cache.CacheType.CLIENT_SERVER_SHARED);
+               this.session = request.getSession();
+       }
+
+       private String getInternalKeyName(String externalKey) {
+
+               return this.getClass().getName() + "::" + getName() + "::" + externalKey;
+       }
+
+       public boolean contains(String key) {
+
+               // Lookup object
+               Object object = session.getAttribute(getInternalKeyName(key));
+               if (object == null || !(object instanceof CacheEntry)) { return false; }
+
+               // Clean cache if it is expired
+               if (new Date().after(((CacheEntry) object).expiration)) {
+                       session.removeAttribute(getInternalKeyName(key));
+                       return false;
+               }
+
+               // OK, we have it
+               return true;
+       }
+
+       public String retrieve(String key) {
+
+               // Lookup object
+               Object object = session.getAttribute(getInternalKeyName(key));
+               if (object == null || !(object instanceof CacheEntry)) { return null; }
+
+               // Clean cache if it is expired
+               if (new Date().after(((CacheEntry) object).expiration)) {
+                       session.removeAttribute(getInternalKeyName(key));
+                       return null;
+               }
+
+               // OK, we have it
+               return ((CacheEntry) object).value;
+       }
+
+       public void store(String key, String value, long duration) {
+
+               session.setAttribute(getInternalKeyName(key), new CacheEntry(value, duration));
+       }
+
+}