Index: src/test/java/org/jasig/cas/web/flow/CasFlowExecutionKeyFactoryTests.java =================================================================== --- src/test/java/org/jasig/cas/web/flow/CasFlowExecutionKeyFactoryTests.java (revision 23328) +++ src/test/java/org/jasig/cas/web/flow/CasFlowExecutionKeyFactoryTests.java (working copy) @@ -19,10 +19,6 @@ @Test public void testEncryptionDecryption() throws Exception { final CasFlowExecutionKeyFactory factory = new CasFlowExecutionKeyFactory(mock(ConversationManager.class), mock(FlowExecutionSnapshotFactory.class)); - final String originalString = "5f43a855f34852343434234343Ze1s1"; - final String encryptedVersion = factory.encrypt(originalString); - final String decryptedVersion = factory.decrypt(encryptedVersion); - - assertEquals(originalString, decryptedVersion); + assertNotNull(factory); } } Index: src/main/java/org/jasig/cas/web/flow/CasFlowExecutionKeyFactory.java =================================================================== --- src/main/java/org/jasig/cas/web/flow/CasFlowExecutionKeyFactory.java (revision 23328) +++ src/main/java/org/jasig/cas/web/flow/CasFlowExecutionKeyFactory.java (working copy) @@ -15,19 +15,8 @@ import org.springframework.webflow.execution.repository.snapshot.FlowExecutionSnapshotFactory; import org.springframework.webflow.execution.repository.support.CompositeFlowExecutionKey; -import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.DESKeySpec; -import javax.crypto.spec.IvParameterSpec; import javax.validation.constraints.NotNull; import java.io.Serializable; -import java.nio.charset.Charset; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.security.spec.InvalidKeySpecException; import java.util.UUID; /** @@ -36,124 +25,41 @@ * number of private methods to the sub class in order to have it actually work. This is not a good idea. * * @author Scott Battaglia + * @author Marvin S. Addison * @version $Revision$ $Date$ * @since 3.4.7 */ public final class CasFlowExecutionKeyFactory extends DefaultFlowExecutionRepository { - public static final String DEFAULT_ENCRYPTION_ALGORITHM = "AES"; + private static final String UUID_KEY = "CAS_UUID"; - public static final String DEFAULT_CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; - private boolean defaultBehavior = false; @NotNull private final ConversationManager conversationManager; - @NotNull - private final Key key; - - @NotNull - private final String cipherAlgorithm; - - private final byte[] initialVector = getRandomSalt(16); - - private final IvParameterSpec ivs = new IvParameterSpec(this.initialVector); - - public CasFlowExecutionKeyFactory(final ConversationManager conversationManager, final FlowExecutionSnapshotFactory snapshotFactory) throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException { - this(conversationManager, snapshotFactory, DEFAULT_CIPHER_ALGORITHM, KeyGenerator.getInstance(DEFAULT_ENCRYPTION_ALGORITHM).generateKey()); - } - - public CasFlowExecutionKeyFactory(final ConversationManager conversationManager, final FlowExecutionSnapshotFactory snapshotFactory, final String cipherAlgorithm, final Key secretKey) { + public CasFlowExecutionKeyFactory(final ConversationManager conversationManager, final FlowExecutionSnapshotFactory snapshotFactory) { super(conversationManager, snapshotFactory); this.conversationManager = conversationManager; - this.key = secretKey; - this.cipherAlgorithm = cipherAlgorithm; } - private static byte[] getRandomSalt(final int size) { - final SecureRandom secureRandom = new SecureRandom(); - final byte[] bytes = new byte[size]; - - secureRandom.nextBytes(bytes); - return bytes; - } - - protected String decrypt(final String value) { - if (value == null) { - return null; - } - - try { - final Cipher cipher = Cipher.getInstance(this.cipherAlgorithm); - cipher.init(Cipher.DECRYPT_MODE, this.key, this.ivs); - return new String(cipher.doFinal(hexStringToByteArray(value))); - } catch (final Exception e) { - throw new RuntimeException(e); - } - } - - protected String encrypt(final String value) { - if (value == null) { - return null; - } - - try { - final Cipher cipher = Cipher.getInstance(this.cipherAlgorithm); - cipher.init(Cipher.ENCRYPT_MODE, this.key, this.ivs); - return byteArrayToHexString(cipher.doFinal(value.getBytes())); - } catch (final Exception e) { - throw new RuntimeException(e); - } - } - -public static String byteArrayToHexString(byte[] b){ - StringBuffer sb = new StringBuffer(b.length * 2); - for (int i = 0; i < b.length; i++){ - int v = b[i] & 0xff; - if (v < 16) { - sb.append('0'); - } - sb.append(Integer.toHexString(v)); - } - return sb.toString().toUpperCase(); - } - - protected static byte[] hexStringToByteArray(final String s) { - byte[] b = new byte[s.length() / 2]; - for (int i = 0; i < b.length; i++){ - int index = i * 2; - int v = Integer.parseInt(s.substring(index, index + 2), 16); - b[i] = (byte)v; - } - return b; - } - public FlowExecutionKey getKey(final FlowExecution execution) { if (this.defaultBehavior) { return super.getKey(execution); } - final CasFlowExecutionKey key = (CasFlowExecutionKey) execution.getKey(); + CasFlowExecutionKey key = (CasFlowExecutionKey) execution.getKey(); if (key == null) { final Conversation conversation = beginConversation(execution); final ConversationId executionId = conversation.getId(); - final Serializable nextSnapshotId = nextSnapshotId(executionId); - final String unencryptedVersion = UUID.randomUUID().toString() + CasFlowExecutionKey.KEY_SEPARATOR + "e" + executionId + "s" + nextSnapshotId; - final String encryptedVersion = encrypt(unencryptedVersion); - return new CasFlowExecutionKey(executionId, nextSnapshotId, encryptedVersion); + key = newKey(conversation, executionId); } else { if (getAlwaysGenerateNewNextKey()) { final Serializable executionId = key.getExecutionId(); - final Serializable snapshotId = nextSnapshotId(key.getExecutionId()); - final String unencryptedVersion = UUID.randomUUID().toString() + CasFlowExecutionKey.KEY_SEPARATOR + "e" + executionId + "s" + snapshotId; - final String encryptedVersion = encrypt(unencryptedVersion); - - return new CasFlowExecutionKey(executionId, snapshotId, encryptedVersion); - } else { - return execution.getKey(); + key = newKey(getConversation(executionId), executionId); } } + return key; } public FlowExecutionKey parseFlowExecutionKey(final String encodedKey) throws FlowExecutionRepositoryException { @@ -161,15 +67,20 @@ return super.parseFlowExecutionKey(encodedKey); } - if (!StringUtils.hasText(encodedKey)) { - throw new BadlyFormattedFlowExecutionKeyException(encodedKey, "The string-encoded flow execution key is required"); + throw new IllegalStateException("The string-encoded flow execution key is required"); } - final String unencryptedVersion = decrypt(encodedKey); - String[] keyParts = CasFlowExecutionKey.keyParts(unencryptedVersion); - Serializable executionId = parseExecutionId(keyParts[0], encodedKey); - Serializable snapshotId = parseSnapshotId(keyParts[1], encodedKey); - return new CasFlowExecutionKey(executionId, snapshotId, encodedKey); + final String[] keyParts = CasFlowExecutionKey.keyParts(encodedKey); + final UUID uuid; + try { + uuid = UUID.fromString(keyParts[0]); + } catch (Exception e) { + throw new BadlyFormattedFlowExecutionKeyException(encodedKey, CasFlowExecutionKey.FORMAT); + } + Serializable executionId = parseExecutionId(keyParts[1], encodedKey); + Serializable snapshotId = parseSnapshotId(keyParts[2], encodedKey); + validateUUID(uuid, executionId); + return new CasFlowExecutionKey(executionId, snapshotId, uuid); } private ConversationId parseExecutionId(final String encodedId, final String encodedKey) throws BadlyFormattedFlowExecutionKeyException { @@ -187,13 +98,37 @@ throw new BadlyFormattedFlowExecutionKeyException(encodedKey, CompositeFlowExecutionKey.getFormat(), e); } } + + private CasFlowExecutionKey newKey(final Conversation conversation, final Serializable executionId) { + final UUID uuid = UUID.randomUUID(); + conversation.lock(); + try { + conversation.putAttribute(UUID_KEY, uuid); + } finally { + conversation.unlock(); + } + return new CasFlowExecutionKey(executionId, nextSnapshotId(executionId), uuid); + } + + private void validateUUID(final UUID givenUUID, final Serializable executionId) { + final Conversation conversation = getConversation(executionId); + conversation.lock(); + try { + final UUID savedUUID = (UUID) conversation.getAttribute(UUID_KEY); + if (!givenUUID.equals(savedUUID)) { + throw new IllegalStateException("UUID component of flow execution key not recognized."); + } + } finally { + conversation.unlock(); + } + } /** * Copied from super-class since its marked as private. * @param execution * @return */ - private Conversation beginConversation(FlowExecution execution) { + private Conversation beginConversation(FlowExecution execution) { ConversationParameters parameters = createConversationParameters(execution); Conversation conversation = this.conversationManager.beginConversation(parameters); return conversation; Index: src/main/java/org/jasig/cas/web/flow/CasFlowExecutionKey.java =================================================================== --- src/main/java/org/jasig/cas/web/flow/CasFlowExecutionKey.java (revision 23328) +++ src/main/java/org/jasig/cas/web/flow/CasFlowExecutionKey.java (working copy) @@ -8,26 +8,66 @@ import org.springframework.webflow.execution.repository.support.CompositeFlowExecutionKey; import java.io.Serializable; +import java.util.UUID; /** + * Creates a flow execution ID of the following form: + *
+ * LT-{UUID}Ze{executionId}s{snapshotId}
+ *
* @author Scott Battaglia
+ * @author Marvin S. Addison
* @version $Revision$ $Date$
* @since 3.4.7
*/
public final class CasFlowExecutionKey extends CompositeFlowExecutionKey {
+ /** Flow execution key string format. */
+ public static final String FORMAT = "LT-{UUID}Ze{executionId}s{snapshotId}";
+
+ /** Serialization version marker. */
+ private static final long serialVersionUID = -1535846700101524714L;
+
+ public final static String KEY_PREFIX = "LT-";
+
public final static String KEY_SEPARATOR = "Z";
- private final String encryptedValue;
+ private final UUID uuid;
- public CasFlowExecutionKey(final Serializable executionId, final Serializable snapshotId, final String encryptedVersion) {
+ /**
+ * Creates a new flow execution ID with a new randomly-generated UUID.
+ *
+ * @param executionId
+ * @param snapshotId
+ */
+ public CasFlowExecutionKey(final Serializable executionId, final Serializable snapshotId) {
+ this(executionId, snapshotId, UUID.randomUUID());
+ }
+
+ /**
+ * Creates a new flow execution ID with the given parameters.
+ *
+ * @param executionId
+ * @param snapshotId
+ * @param uuid
+ */
+ public CasFlowExecutionKey(final Serializable executionId, final Serializable snapshotId, final UUID uuid) {
super(executionId, snapshotId);
- this.encryptedValue = encryptedVersion;
+ this.uuid = uuid;
}
+
+ public UUID getUUID() {
+ return this.uuid;
+ }
@Override
public String toString() {
- return this.encryptedValue;
+ final StringBuilder sb = new StringBuilder(50);
+ sb.append(KEY_PREFIX);
+ sb.append(this.uuid);
+ sb.append(KEY_SEPARATOR);
+ sb.append(super.toString());
+ return sb.toString();
}
@Override
@@ -36,31 +76,24 @@
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
- CasFlowExecutionKey that = (CasFlowExecutionKey) o;
-
- if (encryptedValue != null ? !encryptedValue.equals(that.encryptedValue) : that.encryptedValue != null)
- return false;
-
- return true;
+ return toString().equals(o.toString());
}
@Override
public int hashCode() {
int result = super.hashCode();
- result = 31 * result + (encryptedValue != null ? encryptedValue.hashCode() : 0);
+ result = 31 * result + (this.uuid != null ? this.uuid.hashCode() : 0);
return result;
}
public static String[] keyParts(final String encodedKey) {
final String[] keyParts = new String[3];
final int keySeparatorIndex = encodedKey.indexOf(KEY_SEPARATOR);
- keyParts[2] = encodedKey.substring(0, keySeparatorIndex);
-
+ keyParts[0] = encodedKey.substring(KEY_PREFIX.length(), keySeparatorIndex);
final String originalKey = encodedKey.substring(keySeparatorIndex+1);
-
final String[] originalKeys = CompositeFlowExecutionKey.keyParts(originalKey);
- keyParts[0] = originalKeys[0];
- keyParts[1] = originalKeys[1];
+ keyParts[1] = originalKeys[0];
+ keyParts[2] = originalKeys[1];
return keyParts;
}
Index: src/main/webapp/WEB-INF/cas-servlet.xml
===================================================================
--- src/main/webapp/WEB-INF/cas-servlet.xml (revision 23328)
+++ src/main/webapp/WEB-INF/cas-servlet.xml (working copy)
@@ -163,7 +163,8 @@
-