From 463a04f0a226a1dadb1606de43393e665921164b Mon Sep 17 00:00:00 2001 From: Matthias Wessendorf Date: Mon, 27 Jan 2014 15:05:48 +0100 Subject: [PATCH 1/6] Using the net.iharder.Base64 dependency JAR instead of a copied class --- core/pom.xml | 4 + .../main/java/org/keycloak/util/Base64.java | 2091 --------------- .../java/org/keycloak/util/Base64Url.java | 2 + .../main/java/org/keycloak/util/PemUtils.java | 2 + model/api/pom.xml | 4 + .../org/keycloak/models/utils/Base64.java | 2282 ----------------- .../models/utils/Pbkdf2PasswordEncoder.java | 2 + .../models/utils/SHAPasswordEncoder.java | 2 + pom.xml | 5 + 9 files changed, 21 insertions(+), 4373 deletions(-) delete mode 100755 core/src/main/java/org/keycloak/util/Base64.java delete mode 100755 model/api/src/main/java/org/keycloak/models/utils/Base64.java diff --git a/core/pom.xml b/core/pom.xml index 885edd33ae..49d2275ac3 100755 --- a/core/pom.xml +++ b/core/pom.xml @@ -18,6 +18,10 @@ bcprov-jdk16 provided + + net.iharder + base64 + org.codehaus.jackson jackson-core-asl diff --git a/core/src/main/java/org/keycloak/util/Base64.java b/core/src/main/java/org/keycloak/util/Base64.java deleted file mode 100755 index dde2944238..0000000000 --- a/core/src/main/java/org/keycloak/util/Base64.java +++ /dev/null @@ -1,2091 +0,0 @@ -package org.keycloak.util; - -/** - *

Encodes and decodes to and from Base64 notation.

- *

Homepage: http://iharder.net/base64.

- *

- *

Example:

- *

- * String encoded = Base64.encode( myByteArray ); - *
- * byte[] myByteArray = Base64.decode( encoded ); - *

- *

The options parameter, which appears in a few places, is used to pass - * several pieces of information to the encoder. In the "higher level" methods such as - * encodeBytes( bytes, options ) the options parameter can be used to indicate such - * things as first gzipping the bytes before encoding them, not inserting linefeeds, - * and encoding using the URL-safe and Ordered dialects.

- *

- *

Note, according to RFC3548, - * Section 2.1, implementations should not add line feeds unless explicitly told - * to do so. I've got Base64 set to this behavior now, although earlier versions - * broke lines by default.

- *

- *

The constants defined in Base64 can be OR-ed together to combine options, so you - * might make a call like this:

- *

- * String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES ); - *

to compress the data before encoding it and then making the output have newline characters.

- *

Also...

- * String encoded = Base64.encodeBytes( crazyString.getBytes() ); - *

- *

- *

- *

- * Change Log: - *

- * - *

- *

- * I am placing this code in the Public Domain. Do with it as you will. - * This software comes with no guarantees or warranties but with - * plenty of well-wishing instead! - * Please visit http://iharder.net/base64 - * periodically to check for updates or to contribute improvements. - *

- * - * @author Robert Harder - * @author rob@iharder.net - * @version 2.3.7 - */ -public class Base64 { - -/* ******** P U B L I C F I E L D S ******** */ - - - /** - * No options specified. Value is zero. - */ - public final static int NO_OPTIONS = 0; - - /** - * Specify encoding in first bit. Value is one. - */ - public final static int ENCODE = 1; - - - /** - * Specify decoding in first bit. Value is zero. - */ - public final static int DECODE = 0; - - - /** - * Specify that data should be gzip-compressed in second bit. Value is two. - */ - public final static int GZIP = 2; - - /** - * Specify that gzipped data should not be automatically gunzipped. - */ - public final static int DONT_GUNZIP = 4; - - - /** - * Do break lines when encoding. Value is 8. - */ - public final static int DO_BREAK_LINES = 8; - - /** - * Encode using Base64-like encoding that is URL- and Filename-safe as described - * in Section 4 of RFC3548: - * http://www.faqs.org/rfcs/rfc3548.html. - * It is important to note that data encoded this way is not officially valid Base64, - * or at the very least should not be called Base64 without also specifying that is - * was encoded using the URL- and Filename-safe dialect. - */ - public final static int URL_SAFE = 16; - - - /** - * Encode using the special "ordered" dialect of Base64 described here: - * http://www.faqs.org/qa/rfcc-1940.html. - */ - public final static int ORDERED = 32; - - -/* ******** P R I V A T E F I E L D S ******** */ - - - /** - * Maximum line length (76) of Base64 output. - */ - private final static int MAX_LINE_LENGTH = 76; - - - /** - * The equals sign (=) as a byte. - */ - private final static byte EQUALS_SIGN = (byte) '='; - - - /** - * The new line character (\n) as a byte. - */ - private final static byte NEW_LINE = (byte) '\n'; - - - /** - * Preferred encoding. - */ - private final static String PREFERRED_ENCODING = "US-ASCII"; - - - private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding - private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding - - -/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ - - /** - * The 64 valid Base64 values. - */ - /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ - private final static byte[] _STANDARD_ALPHABET = { - (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', - (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', - (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', - (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', - (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', - (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', - (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', - (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', - (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/' - }; - - - /** - * Translates a Base64 value to either its 6-bit reconstruction value - * or a negative number indicating some other meaning. - */ - private final static byte[] _STANDARD_DECODABET = { - -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 - 62, // Plus sign at decimal 43 - -9, -9, -9, // Decimal 44 - 46 - 63, // Slash at decimal 47 - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' - -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' - -9, -9, -9, -9, -9 // Decimal 123 - 127 - , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 - }; - - -/* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ - - /** - * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: - * http://www.faqs.org/rfcs/rfc3548.html. - * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." - */ - private final static byte[] _URL_SAFE_ALPHABET = { - (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', - (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', - (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', - (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', - (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', - (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', - (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', - (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', - (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '-', (byte) '_' - }; - - /** - * Used in decoding URL- and Filename-safe dialects of Base64. - */ - private final static byte[] _URL_SAFE_DECODABET = { - -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 - -9, // Plus sign at decimal 43 - -9, // Decimal 44 - 62, // Minus sign at decimal 45 - -9, // Decimal 46 - -9, // Slash at decimal 47 - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' - -9, -9, -9, -9, // Decimal 91 - 94 - 63, // Underscore at decimal 95 - -9, // Decimal 96 - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' - -9, -9, -9, -9, -9 // Decimal 123 - 127 - , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 - }; - - -/* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ - - /** - * I don't get the point of this technique, but someone requested it, - * and it is described here: - * http://www.faqs.org/qa/rfcc-1940.html. - */ - private final static byte[] _ORDERED_ALPHABET = { - (byte) '-', - (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', - (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', - (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', - (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', - (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', - (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) '_', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', - (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', - (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', - (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z' - }; - - /** - * Used in decoding the "ordered" dialect of Base64. - */ - private final static byte[] _ORDERED_DECODABET = { - -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 - -9, // Plus sign at decimal 43 - -9, // Decimal 44 - 0, // Minus sign at decimal 45 - -9, // Decimal 46 - -9, // Slash at decimal 47 - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, // Letters 'A' through 'M' - 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, // Letters 'N' through 'Z' - -9, -9, -9, -9, // Decimal 91 - 94 - 37, // Underscore at decimal 95 - -9, // Decimal 96 - 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, // Letters 'a' through 'm' - 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // Letters 'n' through 'z' - -9, -9, -9, -9, -9 // Decimal 123 - 127 - , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 - }; - - -/* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ - - - /** - * Returns one of the _SOMETHING_ALPHABET byte arrays depending on - * the options specified. - * It's possible, though silly, to specify ORDERED and URLSAFE - * in which case one of them will be picked, though there is - * no guarantee as to which one will be picked. - */ - private final static byte[] getAlphabet(int options) { - if ((options & URL_SAFE) == URL_SAFE) { - return _URL_SAFE_ALPHABET; - } else if ((options & ORDERED) == ORDERED) { - return _ORDERED_ALPHABET; - } else { - return _STANDARD_ALPHABET; - } - } // end getAlphabet - - - /** - * Returns one of the _SOMETHING_DECODABET byte arrays depending on - * the options specified. - * It's possible, though silly, to specify ORDERED and URL_SAFE - * in which case one of them will be picked, though there is - * no guarantee as to which one will be picked. - */ - private final static byte[] getDecodabet(int options) { - if ((options & URL_SAFE) == URL_SAFE) { - return _URL_SAFE_DECODABET; - } else if ((options & ORDERED) == ORDERED) { - return _ORDERED_DECODABET; - } else { - return _STANDARD_DECODABET; - } - } // end getAlphabet - - - /** - * Defeats instantiation. - */ - private Base64() { - } - - -/* ******** E N C O D I N G M E T H O D S ******** */ - - - /** - * Encodes up to the first three bytes of array threeBytes - * and returns a four-byte array in Base64 notation. - * The actual number of significant bytes in your array is - * given by numSigBytes. - * The array threeBytes needs only be as big as - * numSigBytes. - * Code can reuse a byte array by passing a four-byte array as b4. - * - * @param b4 A reusable byte array to reduce array instantiation - * @param threeBytes the array to convert - * @param numSigBytes the number of significant bytes in your array - * @return four byte array in Base64 notation. - * @since 1.5.1 - */ - private static byte[] encode3to4(byte[] b4, byte[] threeBytes, int numSigBytes, int options) { - encode3to4(threeBytes, 0, numSigBytes, b4, 0, options); - return b4; - } // end encode3to4 - - - /** - *

Encodes up to three bytes of the array source - * and writes the resulting four Base64 bytes to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accomodate srcOffset + 3 for - * the source array or destOffset + 4 for - * the destination array. - * The actual number of significant bytes in your array is - * given by numSigBytes.

- *

This is the lowest level of the encoding methods with - * all possible parameters.

- * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param numSigBytes the number of significant bytes in your array - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @return the destination array - * @since 1.3 - */ - private static byte[] encode3to4( - byte[] source, int srcOffset, int numSigBytes, - byte[] destination, int destOffset, int options) { - - byte[] ALPHABET = getAlphabet(options); - - // 1 2 3 - // 01234567890123456789012345678901 Bit position - // --------000000001111111122222222 Array position from threeBytes - // --------| || || || | Six bit groups to index ALPHABET - // >>18 >>12 >> 6 >> 0 Right shift necessary - // 0x3f 0x3f 0x3f Additional AND - - // Create buffer with zero-padding if there are only one or two - // significant bytes passed in the array. - // We have to shift left 24 in order to flush out the 1's that appear - // when Java treats a value as negative that is cast from a byte to an int. - int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) - | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) - | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); - - switch (numSigBytes) { - case 3: - destination[destOffset] = ALPHABET[(inBuff >>> 18)]; - destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; - destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f]; - return destination; - - case 2: - destination[destOffset] = ALPHABET[(inBuff >>> 18)]; - destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - - case 1: - destination[destOffset] = ALPHABET[(inBuff >>> 18)]; - destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = EQUALS_SIGN; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - - default: - return destination; - } // end switch - } // end encode3to4 - - - /** - * Performs Base64 encoding on the raw ByteBuffer, - * writing it to the encoded ByteBuffer. - * This is an experimental feature. Currently it does not - * pass along any options (such as {@link #DO_BREAK_LINES} - * or {@link #GZIP}. - * - * @param raw input buffer - * @param encoded output buffer - * @since 2.3 - */ - public static void encode(java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded) { - byte[] raw3 = new byte[3]; - byte[] enc4 = new byte[4]; - - while (raw.hasRemaining()) { - int rem = Math.min(3, raw.remaining()); - raw.get(raw3, 0, rem); - encode3to4(enc4, raw3, rem, NO_OPTIONS); - encoded.put(enc4); - } // end input remaining - } - - - /** - * Performs Base64 encoding on the raw ByteBuffer, - * writing it to the encoded CharBuffer. - * This is an experimental feature. Currently it does not - * pass along any options (such as {@link #DO_BREAK_LINES} - * or {@link #GZIP}. - * - * @param raw input buffer - * @param encoded output buffer - * @since 2.3 - */ - public static void encode(java.nio.ByteBuffer raw, java.nio.CharBuffer encoded) { - byte[] raw3 = new byte[3]; - byte[] enc4 = new byte[4]; - - while (raw.hasRemaining()) { - int rem = Math.min(3, raw.remaining()); - raw.get(raw3, 0, rem); - encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS); - for (int i = 0; i < 4; i++) { - encoded.put((char) (enc4[i] & 0xFF)); - } - } // end input remaining - } - - - /** - * Serializes an object and returns the Base64-encoded - * version of that serialized object. - *

- *

As of v 2.3, if the object - * cannot be serialized or there is another error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- *

- * The object is not GZip-compressed before being encoded. - * - * @param serializableObject The object to encode - * @return The Base64-encoded object - * @throws java.io.IOException if there is an error - * @throws NullPointerException if serializedObject is null - * @since 1.4 - */ - public static String encodeObject(java.io.Serializable serializableObject) - throws java.io.IOException { - return encodeObject(serializableObject, NO_OPTIONS); - } // end encodeObject - - - /** - * Serializes an object and returns the Base64-encoded - * version of that serialized object. - *

- *

As of v 2.3, if the object - * cannot be serialized or there is another error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- *

- * The object is not GZip-compressed before being encoded. - *

- * Example options:

-     *   GZIP: gzip-compresses object before encoding it.
-     *   DO_BREAK_LINES: break lines at 76 characters
-     * 
- *

- * Example: encodeObject( myObj, Base64.GZIP ) or - *

- * Example: encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES ) - * - * @param serializableObject The object to encode - * @param options Specified options - * @return The Base64-encoded object - * @throws java.io.IOException if there is an error - * @see #GZIP - * @see #DO_BREAK_LINES - * @since 2.0 - */ - public static String encodeObject(java.io.Serializable serializableObject, int options) - throws java.io.IOException { - - if (serializableObject == null) { - throw new NullPointerException("Cannot serialize a null object."); - } // end if: null - - // Streams - java.io.ByteArrayOutputStream baos = null; - java.io.OutputStream b64os = null; - java.util.zip.GZIPOutputStream gzos = null; - java.io.ObjectOutputStream oos = null; - - - try { - // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream - baos = new java.io.ByteArrayOutputStream(); - b64os = new OutputStream(baos, ENCODE | options); - if ((options & GZIP) != 0) { - // Gzip - gzos = new java.util.zip.GZIPOutputStream(b64os); - oos = new java.io.ObjectOutputStream(gzos); - } else { - // Not gzipped - oos = new java.io.ObjectOutputStream(b64os); - } - oos.writeObject(serializableObject); - } // end try - catch (java.io.IOException e) { - // Catch it and then throw it immediately so that - // the finally{} block is called for cleanup. - throw e; - } // end catch - finally { - try { - oos.close(); - } catch (Exception e) { - } - try { - gzos.close(); - } catch (Exception e) { - } - try { - b64os.close(); - } catch (Exception e) { - } - try { - baos.close(); - } catch (Exception e) { - } - } // end finally - - // Return value according to relevant encoding. - try { - return new String(baos.toByteArray(), PREFERRED_ENCODING); - } // end try - catch (java.io.UnsupportedEncodingException uue) { - // Fall back to some Java default - return new String(baos.toByteArray()); - } // end catch - - } // end encode - - - /** - * Encodes a byte array into Base64 notation. - * Does not GZip-compress data. - * - * @param source The data to convert - * @return The data in Base64-encoded form - * @throws NullPointerException if source array is null - * @since 1.4 - */ - public static String encodeBytes(byte[] source) { - // Since we're not going to have the GZIP encoding turned on, - // we're not going to have an java.io.IOException thrown, so - // we should not force the user to have to catch it. - String encoded = null; - try { - encoded = encodeBytes(source, 0, source.length, NO_OPTIONS); - } catch (java.io.IOException ex) { - assert false : ex.getMessage(); - } // end catch - assert encoded != null; - return encoded; - } // end encodeBytes - - - /** - * Encodes a byte array into Base64 notation. - *

- * Example options:

-     *   GZIP: gzip-compresses object before encoding it.
-     *   DO_BREAK_LINES: break lines at 76 characters
-     *     Note: Technically, this makes your encoding non-compliant.
-     * 
- *

- * Example: encodeBytes( myData, Base64.GZIP ) or - *

- * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) - *

- *

- *

As of v 2.3, if there is an error with the GZIP stream, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param source The data to convert - * @param options Specified options - * @return The Base64-encoded data as a String - * @throws java.io.IOException if there is an error - * @throws NullPointerException if source array is null - * @see #GZIP - * @see #DO_BREAK_LINES - * @since 2.0 - */ - public static String encodeBytes(byte[] source, int options) throws java.io.IOException { - return encodeBytes(source, 0, source.length, options); - } // end encodeBytes - - - /** - * Encodes a byte array into Base64 notation. - * Does not GZip-compress data. - *

- *

As of v 2.3, if there is an error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @return The Base64-encoded data as a String - * @throws NullPointerException if source array is null - * @throws IllegalArgumentException if source array, offset, or length are invalid - * @since 1.4 - */ - public static String encodeBytes(byte[] source, int off, int len) { - // Since we're not going to have the GZIP encoding turned on, - // we're not going to have an java.io.IOException thrown, so - // we should not force the user to have to catch it. - String encoded = null; - try { - encoded = encodeBytes(source, off, len, NO_OPTIONS); - } catch (java.io.IOException ex) { - assert false : ex.getMessage(); - } // end catch - assert encoded != null; - return encoded; - } // end encodeBytes - - - /** - * Encodes a byte array into Base64 notation. - *

- * Example options:

-     *   GZIP: gzip-compresses object before encoding it.
-     *   DO_BREAK_LINES: break lines at 76 characters
-     *     Note: Technically, this makes your encoding non-compliant.
-     * 
- *

- * Example: encodeBytes( myData, Base64.GZIP ) or - *

- * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) - *

- *

- *

As of v 2.3, if there is an error with the GZIP stream, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param options Specified options - * @return The Base64-encoded data as a String - * @throws java.io.IOException if there is an error - * @throws NullPointerException if source array is null - * @throws IllegalArgumentException if source array, offset, or length are invalid - * @see #GZIP - * @see #DO_BREAK_LINES - * @since 2.0 - */ - public static String encodeBytes(byte[] source, int off, int len, int options) throws java.io.IOException { - byte[] encoded = encodeBytesToBytes(source, off, len, options); - - // Return value according to relevant encoding. - try { - return new String(encoded, PREFERRED_ENCODING); - } // end try - catch (java.io.UnsupportedEncodingException uue) { - return new String(encoded); - } // end catch - - } // end encodeBytes - - - /** - * Similar to {@link #encodeBytes(byte[])} but returns - * a byte array instead of instantiating a String. This is more efficient - * if you're working with I/O streams and have large data sets to encode. - * - * @param source The data to convert - * @return The Base64-encoded data as a byte[] (of ASCII characters) - * @throws NullPointerException if source array is null - * @since 2.3.1 - */ - public static byte[] encodeBytesToBytes(byte[] source) { - byte[] encoded = null; - try { - encoded = encodeBytesToBytes(source, 0, source.length, NO_OPTIONS); - } catch (java.io.IOException ex) { - assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); - } - return encoded; - } - - - /** - * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns - * a byte array instead of instantiating a String. This is more efficient - * if you're working with I/O streams and have large data sets to encode. - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param options Specified options - * @return The Base64-encoded data as a String - * @throws java.io.IOException if there is an error - * @throws NullPointerException if source array is null - * @throws IllegalArgumentException if source array, offset, or length are invalid - * @see #GZIP - * @see #DO_BREAK_LINES - * @since 2.3.1 - */ - public static byte[] encodeBytesToBytes(byte[] source, int off, int len, int options) throws java.io.IOException { - - if (source == null) { - throw new NullPointerException("Cannot serialize a null array."); - } // end if: null - - if (off < 0) { - throw new IllegalArgumentException("Cannot have negative offset: " + off); - } // end if: off < 0 - - if (len < 0) { - throw new IllegalArgumentException("Cannot have length offset: " + len); - } // end if: len < 0 - - if (off + len > source.length) { - throw new IllegalArgumentException( - String.format("Cannot have offset of %d and length of %d with array of length %d", off, len, source.length)); - } // end if: off < 0 - - - // Compress? - if ((options & GZIP) != 0) { - java.io.ByteArrayOutputStream baos = null; - java.util.zip.GZIPOutputStream gzos = null; - OutputStream b64os = null; - - try { - // GZip -> Base64 -> ByteArray - baos = new java.io.ByteArrayOutputStream(); - b64os = new OutputStream(baos, ENCODE | options); - gzos = new java.util.zip.GZIPOutputStream(b64os); - - gzos.write(source, off, len); - gzos.close(); - } // end try - catch (java.io.IOException e) { - // Catch it and then throw it immediately so that - // the finally{} block is called for cleanup. - throw e; - } // end catch - finally { - try { - gzos.close(); - } catch (Exception e) { - } - try { - b64os.close(); - } catch (Exception e) { - } - try { - baos.close(); - } catch (Exception e) { - } - } // end finally - - return baos.toByteArray(); - } // end if: compress - - // Else, don't compress. Better not to use streams at all then. - else { - boolean breakLines = (options & DO_BREAK_LINES) != 0; - - //int len43 = len * 4 / 3; - //byte[] outBuff = new byte[ ( len43 ) // Main 4:3 - // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding - // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines - // Try to determine more precisely how big the array needs to be. - // If we get it right, we don't have to do an array copy, and - // we save a bunch of memory. - int encLen = (len / 3) * 4 + (len % 3 > 0 ? 4 : 0); // Bytes needed for actual encoding - if (breakLines) { - encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters - } - byte[] outBuff = new byte[encLen]; - - - int d = 0; - int e = 0; - int len2 = len - 2; - int lineLength = 0; - for (; d < len2; d += 3, e += 4) { - encode3to4(source, d + off, 3, outBuff, e, options); - - lineLength += 4; - if (breakLines && lineLength >= MAX_LINE_LENGTH) { - outBuff[e + 4] = NEW_LINE; - e++; - lineLength = 0; - } // end if: end of line - } // en dfor: each piece of array - - if (d < len) { - encode3to4(source, d + off, len - d, outBuff, e, options); - e += 4; - } // end if: some padding needed - - - // Only resize array if we didn't guess it right. - if (e <= outBuff.length - 1) { - // If breaking lines and the last byte falls right at - // the line length (76 bytes per line), there will be - // one extra byte, and the array will need to be resized. - // Not too bad of an estimate on array size, I'd say. - byte[] finalOut = new byte[e]; - System.arraycopy(outBuff, 0, finalOut, 0, e); - //System.err.println("Having to resize array from " + outBuff.length + " to " + e ); - return finalOut; - } else { - //System.err.println("No need to resize array."); - return outBuff; - } - - } // end else: don't compress - - } // end encodeBytesToBytes - - -/* ******** D E C O D I N G M E T H O D S ******** */ - - - /** - * Decodes four bytes from array source - * and writes the resulting bytes (up to three of them) - * to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accomodate srcOffset + 4 for - * the source array or destOffset + 3 for - * the destination array. - * This method returns the actual number of bytes that - * were converted from the Base64 encoding. - *

This is the lowest level of the decoding methods with - * all possible parameters.

- * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @param options alphabet type is pulled from this (standard, url-safe, ordered) - * @return the number of decoded bytes converted - * @throws NullPointerException if source or destination arrays are null - * @throws IllegalArgumentException if srcOffset or destOffset are invalid - * or there is not enough room in the array. - * @since 1.3 - */ - private static int decode4to3( - byte[] source, int srcOffset, - byte[] destination, int destOffset, int options) { - - // Lots of error checking and exception throwing - if (source == null) { - throw new NullPointerException("Source array was null."); - } // end if - if (destination == null) { - throw new NullPointerException("Destination array was null."); - } // end if - if (srcOffset < 0 || srcOffset + 3 >= source.length) { - throw new IllegalArgumentException(String.format( - "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset)); - } // end if - if (destOffset < 0 || destOffset + 2 >= destination.length) { - throw new IllegalArgumentException(String.format( - "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset)); - } // end if - - - byte[] DECODABET = getDecodabet(options); - - // Example: Dk== - if (source[srcOffset + 2] == EQUALS_SIGN) { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); - int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) - | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12); - - destination[destOffset] = (byte) (outBuff >>> 16); - return 1; - } - - // Example: DkL= - else if (source[srcOffset + 3] == EQUALS_SIGN) { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); - int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) - | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) - | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6); - - destination[destOffset] = (byte) (outBuff >>> 16); - destination[destOffset + 1] = (byte) (outBuff >>> 8); - return 2; - } - - // Example: DkLE - else { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) - // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); - int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) - | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) - | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6) - | ((DECODABET[source[srcOffset + 3]] & 0xFF)); - - - destination[destOffset] = (byte) (outBuff >> 16); - destination[destOffset + 1] = (byte) (outBuff >> 8); - destination[destOffset + 2] = (byte) (outBuff); - - return 3; - } - } // end decodeToBytes - - - /** - * Low-level access to decoding ASCII characters in - * the form of a byte array. Ignores GUNZIP option, if - * it's set. This is not generally a recommended method, - * although it is used internally as part of the decoding process. - * Special case: if len = 0, an empty array is returned. Still, - * if you need more speed and reduced memory footprint (and aren't - * gzipping), consider this method. - * - * @param source The Base64 encoded data - * @return decoded data - * @since 2.3.1 - */ - public static byte[] decode(byte[] source) - throws java.io.IOException { - byte[] decoded = null; -// try { - decoded = decode(source, 0, source.length, NO_OPTIONS); -// } catch( java.io.IOException ex ) { -// assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); -// } - return decoded; - } - - - /** - * Low-level access to decoding ASCII characters in - * the form of a byte array. Ignores GUNZIP option, if - * it's set. This is not generally a recommended method, - * although it is used internally as part of the decoding process. - * Special case: if len = 0, an empty array is returned. Still, - * if you need more speed and reduced memory footprint (and aren't - * gzipping), consider this method. - * - * @param source The Base64 encoded data - * @param off The offset of where to begin decoding - * @param len The length of characters to decode - * @param options Can specify options such as alphabet type to use - * @return decoded data - * @throws java.io.IOException If bogus characters exist in source data - * @since 1.3 - */ - public static byte[] decode(byte[] source, int off, int len, int options) - throws java.io.IOException { - - // Lots of error checking and exception throwing - if (source == null) { - throw new NullPointerException("Cannot decode null source array."); - } // end if - if (off < 0 || off + len > source.length) { - throw new IllegalArgumentException(String.format( - "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len)); - } // end if - - if (len == 0) { - return new byte[0]; - } else if (len < 4) { - throw new IllegalArgumentException( - "Base64-encoded string must have at least four characters, but length specified was " + len); - } // end if - - byte[] DECODABET = getDecodabet(options); - - int len34 = len * 3 / 4; // Estimate on array size - byte[] outBuff = new byte[len34]; // Upper limit on size of output - int outBuffPosn = 0; // Keep track of where we're writing - - byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space - int b4Posn = 0; // Keep track of four byte input buffer - int i = 0; // Source array counter - byte sbiDecode = 0; // Special value from DECODABET - - for (i = off; i < off + len; i++) { // Loop through source - - sbiDecode = DECODABET[source[i] & 0xFF]; - - // White space, Equals sign, or legit Base64 character - // Note the values such as -5 and -9 in the - // DECODABETs at the top of the file. - if (sbiDecode >= WHITE_SPACE_ENC) { - if (sbiDecode >= EQUALS_SIGN_ENC) { - b4[b4Posn++] = source[i]; // Save non-whitespace - if (b4Posn > 3) { // Time to decode? - outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, options); - b4Posn = 0; - - // If that was the equals sign, break out of 'for' loop - if (source[i] == EQUALS_SIGN) { - break; - } // end if: equals sign - } // end if: quartet built - } // end if: equals sign or better - } // end if: white space, equals sign or better - else { - // There's a bad input character in the Base64 stream. - throw new java.io.IOException(String.format( - "Bad Base64 input character decimal %d in array position %d", ((int) source[i]) & 0xFF, i)); - } // end else: - } // each input character - - byte[] out = new byte[outBuffPosn]; - System.arraycopy(outBuff, 0, out, 0, outBuffPosn); - return out; - } // end decode - - - /** - * Decodes data from Base64 notation, automatically - * detecting gzip-compressed data and decompressing it. - * - * @param s the string to decode - * @return the decoded data - * @throws java.io.IOException If there is a problem - * @since 1.4 - */ - public static byte[] decode(String s) throws java.io.IOException { - return decode(s, NO_OPTIONS); - } - - - /** - * Decodes data from Base64 notation, automatically - * detecting gzip-compressed data and decompressing it. - * - * @param s the string to decode - * @param options encode options such as URL_SAFE - * @return the decoded data - * @throws java.io.IOException if there is an error - * @throws NullPointerException if s is null - * @since 1.4 - */ - public static byte[] decode(String s, int options) throws java.io.IOException { - - if (s == null) { - throw new NullPointerException("Input string was null."); - } // end if - - byte[] bytes; - try { - bytes = s.getBytes(PREFERRED_ENCODING); - } // end try - catch (java.io.UnsupportedEncodingException uee) { - bytes = s.getBytes(); - } // end catch - // - - // Decode - bytes = decode(bytes, 0, bytes.length, options); - - // Check to see if it's gzip-compressed - // GZIP Magic Two-Byte Number: 0x8b1f (35615) - boolean dontGunzip = (options & DONT_GUNZIP) != 0; - if ((bytes != null) && (bytes.length >= 4) && (!dontGunzip)) { - - int head = ((int) bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); - if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) { - java.io.ByteArrayInputStream bais = null; - java.util.zip.GZIPInputStream gzis = null; - java.io.ByteArrayOutputStream baos = null; - byte[] buffer = new byte[2048]; - int length = 0; - - try { - baos = new java.io.ByteArrayOutputStream(); - bais = new java.io.ByteArrayInputStream(bytes); - gzis = new java.util.zip.GZIPInputStream(bais); - - while ((length = gzis.read(buffer)) >= 0) { - baos.write(buffer, 0, length); - } // end while: reading input - - // No error? Get new bytes. - bytes = baos.toByteArray(); - - } // end try - catch (java.io.IOException e) { - e.printStackTrace(); - // Just return originally-decoded bytes - } // end catch - finally { - try { - baos.close(); - } catch (Exception e) { - } - try { - gzis.close(); - } catch (Exception e) { - } - try { - bais.close(); - } catch (Exception e) { - } - } // end finally - - } // end if: gzipped - } // end if: bytes.length >= 2 - - return bytes; - } // end decode - - - /** - * Attempts to decode Base64 data and deserialize a Java - * Object within. Returns null if there was an error. - * - * @param encodedObject The Base64 data to decode - * @return The decoded and deserialized object - * @throws NullPointerException if encodedObject is null - * @throws java.io.IOException if there is a general error - * @throws ClassNotFoundException if the decoded object is of a - * class that cannot be found by the JVM - * @since 1.5 - */ - public static Object decodeToObject(String encodedObject) - throws java.io.IOException, ClassNotFoundException { - return decodeToObject(encodedObject, NO_OPTIONS, null); - } - - - /** - * Attempts to decode Base64 data and deserialize a Java - * Object within. Returns null if there was an error. - * If loader is not null, it will be the class loader - * used when deserializing. - * - * @param encodedObject The Base64 data to decode - * @param options Various parameters related to decoding - * @param loader Optional class loader to use in deserializing classes. - * @return The decoded and deserialized object - * @throws NullPointerException if encodedObject is null - * @throws java.io.IOException if there is a general error - * @throws ClassNotFoundException if the decoded object is of a - * class that cannot be found by the JVM - * @since 2.3.4 - */ - public static Object decodeToObject( - String encodedObject, int options, final ClassLoader loader) - throws java.io.IOException, ClassNotFoundException { - - // Decode and gunzip if necessary - byte[] objBytes = decode(encodedObject, options); - - java.io.ByteArrayInputStream bais = null; - java.io.ObjectInputStream ois = null; - Object obj = null; - - try { - bais = new java.io.ByteArrayInputStream(objBytes); - - // If no custom class loader is provided, use Java's builtin OIS. - if (loader == null) { - ois = new java.io.ObjectInputStream(bais); - } // end if: no loader provided - - // Else make a customized object input stream that uses - // the provided class loader. - else { - ois = new java.io.ObjectInputStream(bais) { - @Override - public Class resolveClass(java.io.ObjectStreamClass streamClass) - throws java.io.IOException, ClassNotFoundException { - Class c = Class.forName(streamClass.getName(), false, loader); - if (c == null) { - return super.resolveClass(streamClass); - } else { - return c; // Class loader knows of this class. - } // end else: not null - } // end resolveClass - }; // end ois - } // end else: no custom class loader - - obj = ois.readObject(); - } // end try - catch (java.io.IOException e) { - throw e; // Catch and throw in order to execute finally{} - } // end catch - catch (ClassNotFoundException e) { - throw e; // Catch and throw in order to execute finally{} - } // end catch - finally { - try { - bais.close(); - } catch (Exception e) { - } - try { - ois.close(); - } catch (Exception e) { - } - } // end finally - - return obj; - } // end decodeObject - - - /** - * Convenience method for encoding data to a file. - *

- *

As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param dataToEncode byte array of data to encode in base64 form - * @param filename Filename for saving encoded data - * @throws java.io.IOException if there is an error - * @throws NullPointerException if dataToEncode is null - * @since 2.1 - */ - public static void encodeToFile(byte[] dataToEncode, String filename) - throws java.io.IOException { - - if (dataToEncode == null) { - throw new NullPointerException("Data to encode was null."); - } // end iff - - OutputStream bos = null; - try { - bos = new OutputStream( - new java.io.FileOutputStream(filename), ENCODE); - bos.write(dataToEncode); - } // end try - catch (java.io.IOException e) { - throw e; // Catch and throw to execute finally{} block - } // end catch: java.io.IOException - finally { - try { - bos.close(); - } catch (Exception e) { - } - } // end finally - - } // end encodeToFile - - - /** - * Convenience method for decoding data to a file. - *

- *

As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param dataToDecode Base64-encoded data as a string - * @param filename Filename for saving decoded data - * @throws java.io.IOException if there is an error - * @since 2.1 - */ - public static void decodeToFile(String dataToDecode, String filename) - throws java.io.IOException { - - OutputStream bos = null; - try { - bos = new OutputStream( - new java.io.FileOutputStream(filename), DECODE); - bos.write(dataToDecode.getBytes(PREFERRED_ENCODING)); - } // end try - catch (java.io.IOException e) { - throw e; // Catch and throw to execute finally{} block - } // end catch: java.io.IOException - finally { - try { - bos.close(); - } catch (Exception e) { - } - } // end finally - - } // end decodeToFile - - - /** - * Convenience method for reading a base64-encoded - * file and decoding it. - *

- *

As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param filename Filename for reading encoded data - * @return decoded byte array - * @throws java.io.IOException if there is an error - * @since 2.1 - */ - public static byte[] decodeFromFile(String filename) - throws java.io.IOException { - - byte[] decodedData = null; - InputStream bis = null; - try { - // Set up some useful variables - java.io.File file = new java.io.File(filename); - byte[] buffer = null; - int length = 0; - int numBytes = 0; - - // Check for size of file - if (file.length() > Integer.MAX_VALUE) { - throw new java.io.IOException("File is too big for this convenience method (" + file.length() + " bytes)."); - } // end if: file too big for int index - buffer = new byte[(int) file.length()]; - - // Open a stream - bis = new InputStream( - new java.io.BufferedInputStream( - new java.io.FileInputStream(file)), DECODE); - - // Read until done - while ((numBytes = bis.read(buffer, length, 4096)) >= 0) { - length += numBytes; - } // end while - - // Save in a variable to return - decodedData = new byte[length]; - System.arraycopy(buffer, 0, decodedData, 0, length); - - } // end try - catch (java.io.IOException e) { - throw e; // Catch and release to execute finally{} - } // end catch: java.io.IOException - finally { - try { - bis.close(); - } catch (Exception e) { - } - } // end finally - - return decodedData; - } // end decodeFromFile - - - /** - * Convenience method for reading a binary file - * and base64-encoding it. - *

- *

As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param filename Filename for reading binary data - * @return base64-encoded string - * @throws java.io.IOException if there is an error - * @since 2.1 - */ - public static String encodeFromFile(String filename) - throws java.io.IOException { - - String encodedData = null; - InputStream bis = null; - try { - // Set up some useful variables - java.io.File file = new java.io.File(filename); - byte[] buffer = new byte[Math.max((int) (file.length() * 1.4 + 1), 40)]; // Need max() for math on small files (v2.2.1); Need +1 for a few corner cases (v2.3.5) - int length = 0; - int numBytes = 0; - - // Open a stream - bis = new InputStream( - new java.io.BufferedInputStream( - new java.io.FileInputStream(file)), ENCODE); - - // Read until done - while ((numBytes = bis.read(buffer, length, 4096)) >= 0) { - length += numBytes; - } // end while - - // Save in a variable to return - encodedData = new String(buffer, 0, length, PREFERRED_ENCODING); - - } // end try - catch (java.io.IOException e) { - throw e; // Catch and release to execute finally{} - } // end catch: java.io.IOException - finally { - try { - bis.close(); - } catch (Exception e) { - } - } // end finally - - return encodedData; - } // end encodeFromFile - - /** - * Reads infile and encodes it to outfile. - * - * @param infile Input file - * @param outfile Output file - * @throws java.io.IOException if there is an error - * @since 2.2 - */ - public static void encodeFileToFile(String infile, String outfile) - throws java.io.IOException { - - String encoded = encodeFromFile(infile); - java.io.OutputStream out = null; - try { - out = new java.io.BufferedOutputStream( - new java.io.FileOutputStream(outfile)); - out.write(encoded.getBytes("US-ASCII")); // Strict, 7-bit output. - } // end try - catch (java.io.IOException e) { - throw e; // Catch and release to execute finally{} - } // end catch - finally { - try { - out.close(); - } catch (Exception ex) { - } - } // end finally - } // end encodeFileToFile - - - /** - * Reads infile and decodes it to outfile. - * - * @param infile Input file - * @param outfile Output file - * @throws java.io.IOException if there is an error - * @since 2.2 - */ - public static void decodeFileToFile(String infile, String outfile) - throws java.io.IOException { - - byte[] decoded = decodeFromFile(infile); - java.io.OutputStream out = null; - try { - out = new java.io.BufferedOutputStream( - new java.io.FileOutputStream(outfile)); - out.write(decoded); - } // end try - catch (java.io.IOException e) { - throw e; // Catch and release to execute finally{} - } // end catch - finally { - try { - out.close(); - } catch (Exception ex) { - } - } // end finally - } // end decodeFileToFile - - - /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ - - - /** - * A {@link InputStream} will read data from another - * java.io.InputStream, given in the constructor, - * and encode/decode to/from Base64 notation on the fly. - * - * @see Base64 - * @since 1.3 - */ - public static class InputStream extends java.io.FilterInputStream { - - private boolean encode; // Encoding or decoding - private int position; // Current position in the buffer - private byte[] buffer; // Small buffer holding converted data - private int bufferLength; // Length of buffer (3 or 4) - private int numSigBytes; // Number of meaningful bytes in the buffer - private int lineLength; - private boolean breakLines; // Break lines at less than 80 characters - private int options; // Record options used to create the stream. - private byte[] decodabet; // Local copies to avoid extra method calls - - - /** - * Constructs a {@link InputStream} in DECODE mode. - * - * @param in the java.io.InputStream from which to read data. - * @since 1.3 - */ - public InputStream(java.io.InputStream in) { - this(in, DECODE); - } // end constructor - - - /** - * Constructs a {@link InputStream} in - * either ENCODE or DECODE mode. - *

- * Valid options:

-         *   ENCODE or DECODE: Encode or Decode as data is read.
-         *   DO_BREAK_LINES: break lines at 76 characters
-         *     (only meaningful when encoding)
-         * 
- *

- * Example: new Base64.InputStream( in, Base64.DECODE ) - * - * @param in the java.io.InputStream from which to read data. - * @param options Specified options - * @see #ENCODE - * @see #DECODE - * @see #DO_BREAK_LINES - * @since 2.0 - */ - public InputStream(java.io.InputStream in, int options) { - - super(in); - this.options = options; // Record for later - this.breakLines = (options & DO_BREAK_LINES) > 0; - this.encode = (options & ENCODE) > 0; - this.bufferLength = encode ? 4 : 3; - this.buffer = new byte[bufferLength]; - this.position = -1; - this.lineLength = 0; - this.decodabet = getDecodabet(options); - } // end constructor - - /** - * Reads enough of the input stream to convert - * to/from Base64 and returns the next byte. - * - * @return next byte - * @since 1.3 - */ - @Override - public int read() throws java.io.IOException { - - // Do we need to get data? - if (position < 0) { - if (encode) { - byte[] b3 = new byte[3]; - int numBinaryBytes = 0; - for (int i = 0; i < 3; i++) { - int b = in.read(); - - // If end of stream, b is -1. - if (b >= 0) { - b3[i] = (byte) b; - numBinaryBytes++; - } else { - break; // out of for loop - } // end else: end of stream - - } // end for: each needed input byte - - if (numBinaryBytes > 0) { - encode3to4(b3, 0, numBinaryBytes, buffer, 0, options); - position = 0; - numSigBytes = 4; - } // end if: got data - else { - return -1; // Must be end of stream - } // end else - } // end if: encoding - - // Else decoding - else { - byte[] b4 = new byte[4]; - int i = 0; - for (i = 0; i < 4; i++) { - // Read four "meaningful" bytes: - int b = 0; - do { - b = in.read(); - } - while (b >= 0 && decodabet[b & 0x7f] <= WHITE_SPACE_ENC); - - if (b < 0) { - break; // Reads a -1 if end of stream - } // end if: end of stream - - b4[i] = (byte) b; - } // end for: each needed input byte - - if (i == 4) { - numSigBytes = decode4to3(b4, 0, buffer, 0, options); - position = 0; - } // end if: got four characters - else if (i == 0) { - return -1; - } // end else if: also padded correctly - else { - // Must have broken out from above. - throw new java.io.IOException("Improperly padded Base64 input."); - } // end - - } // end else: decode - } // end else: get data - - // Got data? - if (position >= 0) { - // End of relevant data? - if ( /*!encode &&*/ position >= numSigBytes) { - return -1; - } // end if: got data - - if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) { - lineLength = 0; - return '\n'; - } // end if - else { - lineLength++; // This isn't important when decoding - // but throwing an extra "if" seems - // just as wasteful. - - int b = buffer[position++]; - - if (position >= bufferLength) { - position = -1; - } // end if: end - - return b & 0xFF; // This is how you "cast" a byte that's - // intended to be unsigned. - } // end else - } // end if: position >= 0 - - // Else error - else { - throw new java.io.IOException("Error in Base64 code reading stream."); - } // end else - } // end read - - - /** - * Calls {@link #read()} repeatedly until the end of stream - * is reached or len bytes are read. - * Returns number of bytes read into array or -1 if - * end of stream is encountered. - * - * @param dest array to hold values - * @param off offset for array - * @param len max number of bytes to read into array - * @return bytes read into array or -1 if end of stream is encountered. - * @since 1.3 - */ - @Override - public int read(byte[] dest, int off, int len) - throws java.io.IOException { - int i; - int b; - for (i = 0; i < len; i++) { - b = read(); - - if (b >= 0) { - dest[off + i] = (byte) b; - } else if (i == 0) { - return -1; - } else { - break; // Out of 'for' loop - } // Out of 'for' loop - } // end for: each byte read - return i; - } // end read - - } // end inner class InputStream - - - /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ - - - /** - * A {@link OutputStream} will write data to another - * java.io.OutputStream, given in the constructor, - * and encode/decode to/from Base64 notation on the fly. - * - * @see Base64 - * @since 1.3 - */ - public static class OutputStream extends java.io.FilterOutputStream { - - private boolean encode; - private int position; - private byte[] buffer; - private int bufferLength; - private int lineLength; - private boolean breakLines; - private byte[] b4; // Scratch used in a few places - private boolean suspendEncoding; - private int options; // Record for later - private byte[] decodabet; // Local copies to avoid extra method calls - - /** - * Constructs a {@link OutputStream} in ENCODE mode. - * - * @param out the java.io.OutputStream to which data will be written. - * @since 1.3 - */ - public OutputStream(java.io.OutputStream out) { - this(out, ENCODE); - } // end constructor - - - /** - * Constructs a {@link OutputStream} in - * either ENCODE or DECODE mode. - *

- * Valid options:

-         *   ENCODE or DECODE: Encode or Decode as data is read.
-         *   DO_BREAK_LINES: don't break lines at 76 characters
-         *     (only meaningful when encoding)
-         * 
- *

- * Example: new Base64.OutputStream( out, Base64.ENCODE ) - * - * @param out the java.io.OutputStream to which data will be written. - * @param options Specified options. - * @see #ENCODE - * @see #DECODE - * @see #DO_BREAK_LINES - * @since 1.3 - */ - public OutputStream(java.io.OutputStream out, int options) { - super(out); - this.breakLines = (options & DO_BREAK_LINES) != 0; - this.encode = (options & ENCODE) != 0; - this.bufferLength = encode ? 3 : 4; - this.buffer = new byte[bufferLength]; - this.position = 0; - this.lineLength = 0; - this.suspendEncoding = false; - this.b4 = new byte[4]; - this.options = options; - this.decodabet = getDecodabet(options); - } // end constructor - - - /** - * Writes the byte to the output stream after - * converting to/from Base64 notation. - * When encoding, bytes are buffered three - * at a time before the output stream actually - * gets a write() call. - * When decoding, bytes are buffered four - * at a time. - * - * @param theByte the byte to write - * @since 1.3 - */ - @Override - public void write(int theByte) - throws java.io.IOException { - // Encoding suspended? - if (suspendEncoding) { - this.out.write(theByte); - return; - } // end if: supsended - - // Encode? - if (encode) { - buffer[position++] = (byte) theByte; - if (position >= bufferLength) { // Enough to encode. - - this.out.write(encode3to4(b4, buffer, bufferLength, options)); - - lineLength += 4; - if (breakLines && lineLength >= MAX_LINE_LENGTH) { - this.out.write(NEW_LINE); - lineLength = 0; - } // end if: end of line - - position = 0; - } // end if: enough to output - } // end if: encoding - - // Else, Decoding - else { - // Meaningful Base64 character? - if (decodabet[theByte & 0x7f] > WHITE_SPACE_ENC) { - buffer[position++] = (byte) theByte; - if (position >= bufferLength) { // Enough to output. - - int len = decode4to3(buffer, 0, b4, 0, options); - out.write(b4, 0, len); - position = 0; - } // end if: enough to output - } // end if: meaningful base64 character - else if (decodabet[theByte & 0x7f] != WHITE_SPACE_ENC) { - throw new java.io.IOException("Invalid character in Base64 data."); - } // end else: not white space either - } // end else: decoding - } // end write - - - /** - * Calls {@link #write(int)} repeatedly until len - * bytes are written. - * - * @param theBytes array from which to read bytes - * @param off offset for array - * @param len max number of bytes to read into array - * @since 1.3 - */ - @Override - public void write(byte[] theBytes, int off, int len) - throws java.io.IOException { - // Encoding suspended? - if (suspendEncoding) { - this.out.write(theBytes, off, len); - return; - } // end if: supsended - - for (int i = 0; i < len; i++) { - write(theBytes[off + i]); - } // end for: each byte written - - } // end write - - - /** - * Method added by PHIL. [Thanks, PHIL. -Rob] - * This pads the buffer without closing the stream. - * - * @throws java.io.IOException if there's an error. - */ - public void flushBase64() throws java.io.IOException { - if (position > 0) { - if (encode) { - out.write(encode3to4(b4, buffer, position, options)); - position = 0; - } // end if: encoding - else { - throw new java.io.IOException("Base64 input not properly padded."); - } // end else: decoding - } // end if: buffer partially full - - } // end flush - - - /** - * Flushes and closes (I think, in the superclass) the stream. - * - * @since 1.3 - */ - @Override - public void close() throws java.io.IOException { - // 1. Ensure that pending characters are written - flushBase64(); - - // 2. Actually close the stream - // Base class both flushes and closes. - super.close(); - - buffer = null; - out = null; - } // end close - - - /** - * Suspends encoding of the stream. - * May be helpful if you need to embed a piece of - * base64-encoded data in a stream. - * - * @throws java.io.IOException if there's an error flushing - * @since 1.5.1 - */ - public void suspendEncoding() throws java.io.IOException { - flushBase64(); - this.suspendEncoding = true; - } // end suspendEncoding - - - /** - * Resumes encoding of the stream. - * May be helpful if you need to embed a piece of - * base64-encoded data in a stream. - * - * @since 1.5.1 - */ - public void resumeEncoding() { - this.suspendEncoding = false; - } // end resumeEncoding - - - } // end inner class OutputStream - - -} // end class Base64 diff --git a/core/src/main/java/org/keycloak/util/Base64Url.java b/core/src/main/java/org/keycloak/util/Base64Url.java index 15d2d779b8..e4e63b13af 100755 --- a/core/src/main/java/org/keycloak/util/Base64Url.java +++ b/core/src/main/java/org/keycloak/util/Base64Url.java @@ -1,6 +1,8 @@ package org.keycloak.util; +import net.iharder.Base64; + /** * @author Bill Burke * @version $Revision: 1 $ diff --git a/core/src/main/java/org/keycloak/util/PemUtils.java b/core/src/main/java/org/keycloak/util/PemUtils.java index 6f8979cbbd..66b27ad095 100755 --- a/core/src/main/java/org/keycloak/util/PemUtils.java +++ b/core/src/main/java/org/keycloak/util/PemUtils.java @@ -1,6 +1,8 @@ package org.keycloak.util; +import net.iharder.Base64; + import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.IOException; diff --git a/model/api/pom.xml b/model/api/pom.xml index 9f659fac22..2c64294f4a 100755 --- a/model/api/pom.xml +++ b/model/api/pom.xml @@ -13,6 +13,10 @@ + + net.iharder + base64 + junit junit diff --git a/model/api/src/main/java/org/keycloak/models/utils/Base64.java b/model/api/src/main/java/org/keycloak/models/utils/Base64.java deleted file mode 100755 index 2fa912b11d..0000000000 --- a/model/api/src/main/java/org/keycloak/models/utils/Base64.java +++ /dev/null @@ -1,2282 +0,0 @@ -package org.keycloak.models.utils; - -/** - *

Encodes and decodes to and from Base64 notation.

- *

Homepage: http://iharder.net/base64.

- *

- *

Example:

- *

- * String encoded = Base64.encode( myByteArray ); - *
- * byte[] myByteArray = Base64.decode( encoded ); - *

- *

The options parameter, which appears in a few places, is used to pass - * several pieces of information to the encoder. In the "higher level" methods such as - * encodeBytes( bytes, options ) the options parameter can be used to indicate such - * things as first gzipping the bytes before encoding them, not inserting linefeeds, - * and encoding using the URL-safe and Ordered dialects.

- *

- *

Note, according to RFC3548, - * Section 2.1, implementations should not add line feeds unless explicitly told - * to do so. I've got Base64 set to this behavior now, although earlier versions - * broke lines by default.

- *

- *

The constants defined in Base64 can be OR-ed together to combine options, so you - * might make a call like this:

- *

- * String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES ); - *

to compress the data before encoding it and then making the output have newline characters.

- *

Also...

- * String encoded = Base64.encodeBytes( crazyString.getBytes() ); - *

- *

- *

- *

- * Change Log: - *

- * - *

- *

- * I am placing this code in the Public Domain. Do with it as you will. - * This software comes with no guarantees or warranties but with - * plenty of well-wishing instead! - * Please visit http://iharder.net/base64 - * periodically to check for updates or to contribute improvements. - *

- * - * @author Robert Harder - * @author rob@iharder.net - * @version 2.3.7 - */ -public class Base64 -{ - -/* ******** P U B L I C F I E L D S ******** */ - - - /** - * No options specified. Value is zero. - */ - public final static int NO_OPTIONS = 0; - - /** - * Specify encoding in first bit. Value is one. - */ - public final static int ENCODE = 1; - - - /** - * Specify decoding in first bit. Value is zero. - */ - public final static int DECODE = 0; - - - /** - * Specify that data should be gzip-compressed in second bit. Value is two. - */ - public final static int GZIP = 2; - - /** - * Specify that gzipped data should not be automatically gunzipped. - */ - public final static int DONT_GUNZIP = 4; - - - /** - * Do break lines when encoding. Value is 8. - */ - public final static int DO_BREAK_LINES = 8; - - /** - * Encode using Base64-like encoding that is URL- and Filename-safe as described - * in Section 4 of RFC3548: - * http://www.faqs.org/rfcs/rfc3548.html. - * It is important to note that data encoded this way is not officially valid Base64, - * or at the very least should not be called Base64 without also specifying that is - * was encoded using the URL- and Filename-safe dialect. - */ - public final static int URL_SAFE = 16; - - - /** - * Encode using the special "ordered" dialect of Base64 described here: - * http://www.faqs.org/qa/rfcc-1940.html. - */ - public final static int ORDERED = 32; - - -/* ******** P R I V A T E F I E L D S ******** */ - - - /** - * Maximum line length (76) of Base64 output. - */ - private final static int MAX_LINE_LENGTH = 76; - - - /** - * The equals sign (=) as a byte. - */ - private final static byte EQUALS_SIGN = (byte) '='; - - - /** - * The new line character (\n) as a byte. - */ - private final static byte NEW_LINE = (byte) '\n'; - - - /** - * Preferred encoding. - */ - private final static String PREFERRED_ENCODING = "US-ASCII"; - - - private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding - private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding - - -/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ - - /** - * The 64 valid Base64 values. - */ - /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ - private final static byte[] _STANDARD_ALPHABET = { - (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', - (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', - (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', - (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', - (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', - (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', - (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', - (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', - (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/' - }; - - - /** - * Translates a Base64 value to either its 6-bit reconstruction value - * or a negative number indicating some other meaning. - */ - private final static byte[] _STANDARD_DECODABET = { - -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 - 62, // Plus sign at decimal 43 - -9, -9, -9, // Decimal 44 - 46 - 63, // Slash at decimal 47 - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' - -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' - -9, -9, -9, -9, -9 // Decimal 123 - 127 - , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 - }; - - -/* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ - - /** - * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: - * http://www.faqs.org/rfcs/rfc3548.html. - * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." - */ - private final static byte[] _URL_SAFE_ALPHABET = { - (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', - (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', - (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', - (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', - (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', - (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', - (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', - (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', - (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '-', (byte) '_' - }; - - /** - * Used in decoding URL- and Filename-safe dialects of Base64. - */ - private final static byte[] _URL_SAFE_DECODABET = { - -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 - -9, // Plus sign at decimal 43 - -9, // Decimal 44 - 62, // Minus sign at decimal 45 - -9, // Decimal 46 - -9, // Slash at decimal 47 - 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' - -9, -9, -9, -9, // Decimal 91 - 94 - 63, // Underscore at decimal 95 - -9, // Decimal 96 - 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' - 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' - -9, -9, -9, -9, -9 // Decimal 123 - 127 - , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 - }; - - -/* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ - - /** - * I don't get the point of this technique, but someone requested it, - * and it is described here: - * http://www.faqs.org/qa/rfcc-1940.html. - */ - private final static byte[] _ORDERED_ALPHABET = { - (byte) '-', - (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', - (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', - (byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', - (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', - (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', - (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', - (byte) '_', - (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', - (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', - (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', - (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z' - }; - - /** - * Used in decoding the "ordered" dialect of Base64. - */ - private final static byte[] _ORDERED_DECODABET = { - -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 - -5, -5, // Whitespace: Tab and Linefeed - -9, -9, // Decimal 11 - 12 - -5, // Whitespace: Carriage Return - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 - -9, -9, -9, -9, -9, // Decimal 27 - 31 - -5, // Whitespace: Space - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 - -9, // Plus sign at decimal 43 - -9, // Decimal 44 - 0, // Minus sign at decimal 45 - -9, // Decimal 46 - -9, // Slash at decimal 47 - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, // Numbers zero through nine - -9, -9, -9, // Decimal 58 - 60 - -1, // Equals sign at decimal 61 - -9, -9, -9, // Decimal 62 - 64 - 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, // Letters 'A' through 'M' - 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, // Letters 'N' through 'Z' - -9, -9, -9, -9, // Decimal 91 - 94 - 37, // Underscore at decimal 95 - -9, // Decimal 96 - 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, // Letters 'a' through 'm' - 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, // Letters 'n' through 'z' - -9, -9, -9, -9, -9 // Decimal 123 - 127 - , -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 128 - 139 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 140 - 152 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 153 - 165 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 166 - 178 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 179 - 191 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 192 - 204 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 205 - 217 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 218 - 230 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 231 - 243 - -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9 // Decimal 244 - 255 - }; - - -/* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ - - - /** - * Returns one of the _SOMETHING_ALPHABET byte arrays depending on - * the options specified. - * It's possible, though silly, to specify ORDERED and URLSAFE - * in which case one of them will be picked, though there is - * no guarantee as to which one will be picked. - */ - private final static byte[] getAlphabet(int options) - { - if ((options & URL_SAFE) == URL_SAFE) - { - return _URL_SAFE_ALPHABET; - } - else if ((options & ORDERED) == ORDERED) - { - return _ORDERED_ALPHABET; - } - else - { - return _STANDARD_ALPHABET; - } - } // end getAlphabet - - - /** - * Returns one of the _SOMETHING_DECODABET byte arrays depending on - * the options specified. - * It's possible, though silly, to specify ORDERED and URL_SAFE - * in which case one of them will be picked, though there is - * no guarantee as to which one will be picked. - */ - private final static byte[] getDecodabet(int options) - { - if ((options & URL_SAFE) == URL_SAFE) - { - return _URL_SAFE_DECODABET; - } - else if ((options & ORDERED) == ORDERED) - { - return _ORDERED_DECODABET; - } - else - { - return _STANDARD_DECODABET; - } - } // end getAlphabet - - - /** - * Defeats instantiation. - */ - private Base64() - { - } - - -/* ******** E N C O D I N G M E T H O D S ******** */ - - - /** - * Encodes up to the first three bytes of array threeBytes - * and returns a four-byte array in Base64 notation. - * The actual number of significant bytes in your array is - * given by numSigBytes. - * The array threeBytes needs only be as big as - * numSigBytes. - * Code can reuse a byte array by passing a four-byte array as b4. - * - * @param b4 A reusable byte array to reduce array instantiation - * @param threeBytes the array to convert - * @param numSigBytes the number of significant bytes in your array - * @return four byte array in Base64 notation. - * @since 1.5.1 - */ - private static byte[] encode3to4(byte[] b4, byte[] threeBytes, int numSigBytes, int options) - { - encode3to4(threeBytes, 0, numSigBytes, b4, 0, options); - return b4; - } // end encode3to4 - - - /** - *

Encodes up to three bytes of the array source - * and writes the resulting four Base64 bytes to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accomodate srcOffset + 3 for - * the source array or destOffset + 4 for - * the destination array. - * The actual number of significant bytes in your array is - * given by numSigBytes.

- *

This is the lowest level of the encoding methods with - * all possible parameters.

- * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param numSigBytes the number of significant bytes in your array - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @return the destination array - * @since 1.3 - */ - private static byte[] encode3to4( - byte[] source, int srcOffset, int numSigBytes, - byte[] destination, int destOffset, int options) - { - - byte[] ALPHABET = getAlphabet(options); - - // 1 2 3 - // 01234567890123456789012345678901 Bit position - // --------000000001111111122222222 Array position from threeBytes - // --------| || || || | Six bit groups to index ALPHABET - // >>18 >>12 >> 6 >> 0 Right shift necessary - // 0x3f 0x3f 0x3f Additional AND - - // Create buffer with zero-padding if there are only one or two - // significant bytes passed in the array. - // We have to shift left 24 in order to flush out the 1's that appear - // when Java treats a value as negative that is cast from a byte to an int. - int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) - | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) - | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); - - switch (numSigBytes) - { - case 3: - destination[destOffset] = ALPHABET[(inBuff >>> 18)]; - destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; - destination[destOffset + 3] = ALPHABET[(inBuff) & 0x3f]; - return destination; - - case 2: - destination[destOffset] = ALPHABET[(inBuff >>> 18)]; - destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = ALPHABET[(inBuff >>> 6) & 0x3f]; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - - case 1: - destination[destOffset] = ALPHABET[(inBuff >>> 18)]; - destination[destOffset + 1] = ALPHABET[(inBuff >>> 12) & 0x3f]; - destination[destOffset + 2] = EQUALS_SIGN; - destination[destOffset + 3] = EQUALS_SIGN; - return destination; - - default: - return destination; - } // end switch - } // end encode3to4 - - - /** - * Performs Base64 encoding on the raw ByteBuffer, - * writing it to the encoded ByteBuffer. - * This is an experimental feature. Currently it does not - * pass along any options (such as {@link #DO_BREAK_LINES} - * or {@link #GZIP}. - * - * @param raw input buffer - * @param encoded output buffer - * @since 2.3 - */ - public static void encode(java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded) - { - byte[] raw3 = new byte[3]; - byte[] enc4 = new byte[4]; - - while (raw.hasRemaining()) - { - int rem = Math.min(3, raw.remaining()); - raw.get(raw3, 0, rem); - Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS); - encoded.put(enc4); - } // end input remaining - } - - - /** - * Performs Base64 encoding on the raw ByteBuffer, - * writing it to the encoded CharBuffer. - * This is an experimental feature. Currently it does not - * pass along any options (such as {@link #DO_BREAK_LINES} - * or {@link #GZIP}. - * - * @param raw input buffer - * @param encoded output buffer - * @since 2.3 - */ - public static void encode(java.nio.ByteBuffer raw, java.nio.CharBuffer encoded) - { - byte[] raw3 = new byte[3]; - byte[] enc4 = new byte[4]; - - while (raw.hasRemaining()) - { - int rem = Math.min(3, raw.remaining()); - raw.get(raw3, 0, rem); - Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS); - for (int i = 0; i < 4; i++) - { - encoded.put((char) (enc4[i] & 0xFF)); - } - } // end input remaining - } - - - /** - * Serializes an object and returns the Base64-encoded - * version of that serialized object. - *

- *

As of v 2.3, if the object - * cannot be serialized or there is another error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- *

- * The object is not GZip-compressed before being encoded. - * - * @param serializableObject The object to encode - * @return The Base64-encoded object - * @throws java.io.IOException if there is an error - * @throws NullPointerException if serializedObject is null - * @since 1.4 - */ - public static String encodeObject(java.io.Serializable serializableObject) - throws java.io.IOException - { - return encodeObject(serializableObject, NO_OPTIONS); - } // end encodeObject - - - /** - * Serializes an object and returns the Base64-encoded - * version of that serialized object. - *

- *

As of v 2.3, if the object - * cannot be serialized or there is another error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- *

- * The object is not GZip-compressed before being encoded. - *

- * Example options:

-     *   GZIP: gzip-compresses object before encoding it.
-     *   DO_BREAK_LINES: break lines at 76 characters
-     * 
- *

- * Example: encodeObject( myObj, Base64.GZIP ) or - *

- * Example: encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES ) - * - * @param serializableObject The object to encode - * @param options Specified options - * @return The Base64-encoded object - * @throws java.io.IOException if there is an error - * @see Base64#GZIP - * @see Base64#DO_BREAK_LINES - * @since 2.0 - */ - public static String encodeObject(java.io.Serializable serializableObject, int options) - throws java.io.IOException - { - - if (serializableObject == null) - { - throw new NullPointerException("Cannot serialize a null object."); - } // end if: null - - // Streams - java.io.ByteArrayOutputStream baos = null; - java.io.OutputStream b64os = null; - java.util.zip.GZIPOutputStream gzos = null; - java.io.ObjectOutputStream oos = null; - - - try - { - // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream - baos = new java.io.ByteArrayOutputStream(); - b64os = new Base64.OutputStream(baos, ENCODE | options); - if ((options & GZIP) != 0) - { - // Gzip - gzos = new java.util.zip.GZIPOutputStream(b64os); - oos = new java.io.ObjectOutputStream(gzos); - } - else - { - // Not gzipped - oos = new java.io.ObjectOutputStream(b64os); - } - oos.writeObject(serializableObject); - } // end try - catch (java.io.IOException e) - { - // Catch it and then throw it immediately so that - // the finally{} block is called for cleanup. - throw e; - } // end catch - finally - { - try - { oos.close(); } - catch (Exception e) - {} - try - { gzos.close(); } - catch (Exception e) - {} - try - { b64os.close(); } - catch (Exception e) - {} - try - { baos.close(); } - catch (Exception e) - {} - } // end finally - - // Return value according to relevant encoding. - try - { - return new String(baos.toByteArray(), PREFERRED_ENCODING); - } // end try - catch (java.io.UnsupportedEncodingException uue) - { - // Fall back to some Java default - return new String(baos.toByteArray()); - } // end catch - - } // end encode - - - /** - * Encodes a byte array into Base64 notation. - * Does not GZip-compress data. - * - * @param source The data to convert - * @return The data in Base64-encoded form - * @throws NullPointerException if source array is null - * @since 1.4 - */ - public static String encodeBytes(byte[] source) - { - // Since we're not going to have the GZIP encoding turned on, - // we're not going to have an java.io.IOException thrown, so - // we should not force the user to have to catch it. - String encoded = null; - try - { - encoded = encodeBytes(source, 0, source.length, NO_OPTIONS); - } - catch (java.io.IOException ex) - { - assert false : ex.getMessage(); - } // end catch - assert encoded != null; - return encoded; - } // end encodeBytes - - - /** - * Encodes a byte array into Base64 notation. - *

- * Example options:

-     *   GZIP: gzip-compresses object before encoding it.
-     *   DO_BREAK_LINES: break lines at 76 characters
-     *     Note: Technically, this makes your encoding non-compliant.
-     * 
- *

- * Example: encodeBytes( myData, Base64.GZIP ) or - *

- * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) - *

- *

- *

As of v 2.3, if there is an error with the GZIP stream, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param source The data to convert - * @param options Specified options - * @return The Base64-encoded data as a String - * @throws java.io.IOException if there is an error - * @throws NullPointerException if source array is null - * @see Base64#GZIP - * @see Base64#DO_BREAK_LINES - * @since 2.0 - */ - public static String encodeBytes(byte[] source, int options) throws java.io.IOException - { - return encodeBytes(source, 0, source.length, options); - } // end encodeBytes - - - /** - * Encodes a byte array into Base64 notation. - * Does not GZip-compress data. - *

- *

As of v 2.3, if there is an error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @return The Base64-encoded data as a String - * @throws NullPointerException if source array is null - * @throws IllegalArgumentException if source array, offset, or length are invalid - * @since 1.4 - */ - public static String encodeBytes(byte[] source, int off, int len) - { - // Since we're not going to have the GZIP encoding turned on, - // we're not going to have an java.io.IOException thrown, so - // we should not force the user to have to catch it. - String encoded = null; - try - { - encoded = encodeBytes(source, off, len, NO_OPTIONS); - } - catch (java.io.IOException ex) - { - assert false : ex.getMessage(); - } // end catch - assert encoded != null; - return encoded; - } // end encodeBytes - - - /** - * Encodes a byte array into Base64 notation. - *

- * Example options:

-     *   GZIP: gzip-compresses object before encoding it.
-     *   DO_BREAK_LINES: break lines at 76 characters
-     *     Note: Technically, this makes your encoding non-compliant.
-     * 
- *

- * Example: encodeBytes( myData, Base64.GZIP ) or - *

- * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) - *

- *

- *

As of v 2.3, if there is an error with the GZIP stream, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned a null value, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param options Specified options - * @return The Base64-encoded data as a String - * @throws java.io.IOException if there is an error - * @throws NullPointerException if source array is null - * @throws IllegalArgumentException if source array, offset, or length are invalid - * @see Base64#GZIP - * @see Base64#DO_BREAK_LINES - * @since 2.0 - */ - public static String encodeBytes(byte[] source, int off, int len, int options) throws java.io.IOException - { - byte[] encoded = encodeBytesToBytes(source, off, len, options); - - // Return value according to relevant encoding. - try - { - return new String(encoded, PREFERRED_ENCODING); - } // end try - catch (java.io.UnsupportedEncodingException uue) - { - return new String(encoded); - } // end catch - - } // end encodeBytes - - - /** - * Similar to {@link #encodeBytes(byte[])} but returns - * a byte array instead of instantiating a String. This is more efficient - * if you're working with I/O streams and have large data sets to encode. - * - * @param source The data to convert - * @return The Base64-encoded data as a byte[] (of ASCII characters) - * @throws NullPointerException if source array is null - * @since 2.3.1 - */ - public static byte[] encodeBytesToBytes(byte[] source) - { - byte[] encoded = null; - try - { - encoded = encodeBytesToBytes(source, 0, source.length, Base64.NO_OPTIONS); - } - catch (java.io.IOException ex) - { - assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); - } - return encoded; - } - - - /** - * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns - * a byte array instead of instantiating a String. This is more efficient - * if you're working with I/O streams and have large data sets to encode. - * - * @param source The data to convert - * @param off Offset in array where conversion should begin - * @param len Length of data to convert - * @param options Specified options - * @return The Base64-encoded data as a String - * @throws java.io.IOException if there is an error - * @throws NullPointerException if source array is null - * @throws IllegalArgumentException if source array, offset, or length are invalid - * @see Base64#GZIP - * @see Base64#DO_BREAK_LINES - * @since 2.3.1 - */ - public static byte[] encodeBytesToBytes(byte[] source, int off, int len, int options) throws java.io.IOException - { - - if (source == null) - { - throw new NullPointerException("Cannot serialize a null array."); - } // end if: null - - if (off < 0) - { - throw new IllegalArgumentException("Cannot have negative offset: " + off); - } // end if: off < 0 - - if (len < 0) - { - throw new IllegalArgumentException("Cannot have length offset: " + len); - } // end if: len < 0 - - if (off + len > source.length) - { - throw new IllegalArgumentException( - String.format("Cannot have offset of %d and length of %d with array of length %d", off, len, source.length)); - } // end if: off < 0 - - - // Compress? - if ((options & GZIP) != 0) - { - java.io.ByteArrayOutputStream baos = null; - java.util.zip.GZIPOutputStream gzos = null; - Base64.OutputStream b64os = null; - - try - { - // GZip -> Base64 -> ByteArray - baos = new java.io.ByteArrayOutputStream(); - b64os = new Base64.OutputStream(baos, ENCODE | options); - gzos = new java.util.zip.GZIPOutputStream(b64os); - - gzos.write(source, off, len); - gzos.close(); - } // end try - catch (java.io.IOException e) - { - // Catch it and then throw it immediately so that - // the finally{} block is called for cleanup. - throw e; - } // end catch - finally - { - try - { gzos.close(); } - catch (Exception e) - {} - try - { b64os.close(); } - catch (Exception e) - {} - try - { baos.close(); } - catch (Exception e) - {} - } // end finally - - return baos.toByteArray(); - } // end if: compress - - // Else, don't compress. Better not to use streams at all then. - else - { - boolean breakLines = (options & DO_BREAK_LINES) != 0; - - //int len43 = len * 4 / 3; - //byte[] outBuff = new byte[ ( len43 ) // Main 4:3 - // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding - // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines - // Try to determine more precisely how big the array needs to be. - // If we get it right, we don't have to do an array copy, and - // we save a bunch of memory. - int encLen = (len / 3) * 4 + (len % 3 > 0 ? 4 : 0); // Bytes needed for actual encoding - if (breakLines) - { - encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters - } - byte[] outBuff = new byte[encLen]; - - - int d = 0; - int e = 0; - int len2 = len - 2; - int lineLength = 0; - for (; d < len2; d += 3, e += 4) - { - encode3to4(source, d + off, 3, outBuff, e, options); - - lineLength += 4; - if (breakLines && lineLength >= MAX_LINE_LENGTH) - { - outBuff[e + 4] = NEW_LINE; - e++; - lineLength = 0; - } // end if: end of line - } // en dfor: each piece of array - - if (d < len) - { - encode3to4(source, d + off, len - d, outBuff, e, options); - e += 4; - } // end if: some padding needed - - - // Only resize array if we didn't guess it right. - if (e <= outBuff.length - 1) - { - // If breaking lines and the last byte falls right at - // the line length (76 bytes per line), there will be - // one extra byte, and the array will need to be resized. - // Not too bad of an estimate on array size, I'd say. - byte[] finalOut = new byte[e]; - System.arraycopy(outBuff, 0, finalOut, 0, e); - //System.err.println("Having to resize array from " + outBuff.length + " to " + e ); - return finalOut; - } - else - { - //System.err.println("No need to resize array."); - return outBuff; - } - - } // end else: don't compress - - } // end encodeBytesToBytes - - -/* ******** D E C O D I N G M E T H O D S ******** */ - - - /** - * Decodes four bytes from array source - * and writes the resulting bytes (up to three of them) - * to destination. - * The source and destination arrays can be manipulated - * anywhere along their length by specifying - * srcOffset and destOffset. - * This method does not check to make sure your arrays - * are large enough to accomodate srcOffset + 4 for - * the source array or destOffset + 3 for - * the destination array. - * This method returns the actual number of bytes that - * were converted from the Base64 encoding. - *

This is the lowest level of the decoding methods with - * all possible parameters.

- * - * @param source the array to convert - * @param srcOffset the index where conversion begins - * @param destination the array to hold the conversion - * @param destOffset the index where output will be put - * @param options alphabet type is pulled from this (standard, url-safe, ordered) - * @return the number of decoded bytes converted - * @throws NullPointerException if source or destination arrays are null - * @throws IllegalArgumentException if srcOffset or destOffset are invalid - * or there is not enough room in the array. - * @since 1.3 - */ - private static int decode4to3( - byte[] source, int srcOffset, - byte[] destination, int destOffset, int options) - { - - // Lots of error checking and exception throwing - if (source == null) - { - throw new NullPointerException("Source array was null."); - } // end if - if (destination == null) - { - throw new NullPointerException("Destination array was null."); - } // end if - if (srcOffset < 0 || srcOffset + 3 >= source.length) - { - throw new IllegalArgumentException(String.format( - "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset)); - } // end if - if (destOffset < 0 || destOffset + 2 >= destination.length) - { - throw new IllegalArgumentException(String.format( - "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset)); - } // end if - - - byte[] DECODABET = getDecodabet(options); - - // Example: Dk== - if (source[srcOffset + 2] == EQUALS_SIGN) - { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); - int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) - | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12); - - destination[destOffset] = (byte) (outBuff >>> 16); - return 1; - } - - // Example: DkL= - else if (source[srcOffset + 3] == EQUALS_SIGN) - { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); - int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) - | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) - | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6); - - destination[destOffset] = (byte) (outBuff >>> 16); - destination[destOffset + 1] = (byte) (outBuff >>> 8); - return 2; - } - - // Example: DkLE - else - { - // Two ways to do the same thing. Don't know which way I like best. - //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) - // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) - // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) - // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); - int outBuff = ((DECODABET[source[srcOffset]] & 0xFF) << 18) - | ((DECODABET[source[srcOffset + 1]] & 0xFF) << 12) - | ((DECODABET[source[srcOffset + 2]] & 0xFF) << 6) - | ((DECODABET[source[srcOffset + 3]] & 0xFF)); - - - destination[destOffset] = (byte) (outBuff >> 16); - destination[destOffset + 1] = (byte) (outBuff >> 8); - destination[destOffset + 2] = (byte) (outBuff); - - return 3; - } - } // end decodeToBytes - - - /** - * Low-level access to decoding ASCII characters in - * the form of a byte array. Ignores GUNZIP option, if - * it's set. This is not generally a recommended method, - * although it is used internally as part of the decoding process. - * Special case: if len = 0, an empty array is returned. Still, - * if you need more speed and reduced memory footprint (and aren't - * gzipping), consider this method. - * - * @param source The Base64 encoded data - * @return decoded data - * @since 2.3.1 - */ - public static byte[] decode(byte[] source) - throws java.io.IOException - { - byte[] decoded = null; -// try { - decoded = decode(source, 0, source.length, Base64.NO_OPTIONS); -// } catch( java.io.IOException ex ) { -// assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); -// } - return decoded; - } - - - /** - * Low-level access to decoding ASCII characters in - * the form of a byte array. Ignores GUNZIP option, if - * it's set. This is not generally a recommended method, - * although it is used internally as part of the decoding process. - * Special case: if len = 0, an empty array is returned. Still, - * if you need more speed and reduced memory footprint (and aren't - * gzipping), consider this method. - * - * @param source The Base64 encoded data - * @param off The offset of where to begin decoding - * @param len The length of characters to decode - * @param options Can specify options such as alphabet type to use - * @return decoded data - * @throws java.io.IOException If bogus characters exist in source data - * @since 1.3 - */ - public static byte[] decode(byte[] source, int off, int len, int options) - throws java.io.IOException - { - - // Lots of error checking and exception throwing - if (source == null) - { - throw new NullPointerException("Cannot decode null source array."); - } // end if - if (off < 0 || off + len > source.length) - { - throw new IllegalArgumentException(String.format( - "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len)); - } // end if - - if (len == 0) - { - return new byte[0]; - } - else if (len < 4) - { - throw new IllegalArgumentException( - "Base64-encoded string must have at least four characters, but length specified was " + len); - } // end if - - byte[] DECODABET = getDecodabet(options); - - int len34 = len * 3 / 4; // Estimate on array size - byte[] outBuff = new byte[len34]; // Upper limit on size of output - int outBuffPosn = 0; // Keep track of where we're writing - - byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space - int b4Posn = 0; // Keep track of four byte input buffer - int i = 0; // Source array counter - byte sbiDecode = 0; // Special value from DECODABET - - for (i = off; i < off + len; i++) - { // Loop through source - - sbiDecode = DECODABET[source[i] & 0xFF]; - - // White space, Equals sign, or legit Base64 character - // Note the values such as -5 and -9 in the - // DECODABETs at the top of the file. - if (sbiDecode >= WHITE_SPACE_ENC) - { - if (sbiDecode >= EQUALS_SIGN_ENC) - { - b4[b4Posn++] = source[i]; // Save non-whitespace - if (b4Posn > 3) - { // Time to decode? - outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, options); - b4Posn = 0; - - // If that was the equals sign, break out of 'for' loop - if (source[i] == EQUALS_SIGN) - { - break; - } // end if: equals sign - } // end if: quartet built - } // end if: equals sign or better - } // end if: white space, equals sign or better - else - { - // There's a bad input character in the Base64 stream. - throw new java.io.IOException(String.format( - "Bad Base64 input character decimal %d in array position %d", ((int) source[i]) & 0xFF, i)); - } // end else: - } // each input character - - byte[] out = new byte[outBuffPosn]; - System.arraycopy(outBuff, 0, out, 0, outBuffPosn); - return out; - } // end decode - - - /** - * Decodes data from Base64 notation, automatically - * detecting gzip-compressed data and decompressing it. - * - * @param s the string to decode - * @return the decoded data - * @throws java.io.IOException If there is a problem - * @since 1.4 - */ - public static byte[] decode(String s) throws java.io.IOException - { - return decode(s, NO_OPTIONS); - } - - - /** - * Decodes data from Base64 notation, automatically - * detecting gzip-compressed data and decompressing it. - * - * @param s the string to decode - * @param options encode options such as URL_SAFE - * @return the decoded data - * @throws java.io.IOException if there is an error - * @throws NullPointerException if s is null - * @since 1.4 - */ - public static byte[] decode(String s, int options) throws java.io.IOException - { - - if (s == null) - { - throw new NullPointerException("Input string was null."); - } // end if - - byte[] bytes; - try - { - bytes = s.getBytes(PREFERRED_ENCODING); - } // end try - catch (java.io.UnsupportedEncodingException uee) - { - bytes = s.getBytes(); - } // end catch - // - - // Decode - bytes = decode(bytes, 0, bytes.length, options); - - // Check to see if it's gzip-compressed - // GZIP Magic Two-Byte Number: 0x8b1f (35615) - boolean dontGunzip = (options & DONT_GUNZIP) != 0; - if ((bytes != null) && (bytes.length >= 4) && (!dontGunzip)) - { - - int head = ((int) bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); - if (java.util.zip.GZIPInputStream.GZIP_MAGIC == head) - { - java.io.ByteArrayInputStream bais = null; - java.util.zip.GZIPInputStream gzis = null; - java.io.ByteArrayOutputStream baos = null; - byte[] buffer = new byte[2048]; - int length = 0; - - try - { - baos = new java.io.ByteArrayOutputStream(); - bais = new java.io.ByteArrayInputStream(bytes); - gzis = new java.util.zip.GZIPInputStream(bais); - - while ((length = gzis.read(buffer)) >= 0) - { - baos.write(buffer, 0, length); - } // end while: reading input - - // No error? Get new bytes. - bytes = baos.toByteArray(); - - } // end try - catch (java.io.IOException e) - { - e.printStackTrace(); - // Just return originally-decoded bytes - } // end catch - finally - { - try - { baos.close(); } - catch (Exception e) - {} - try - { gzis.close(); } - catch (Exception e) - {} - try - { bais.close(); } - catch (Exception e) - {} - } // end finally - - } // end if: gzipped - } // end if: bytes.length >= 2 - - return bytes; - } // end decode - - - /** - * Attempts to decode Base64 data and deserialize a Java - * Object within. Returns null if there was an error. - * - * @param encodedObject The Base64 data to decode - * @return The decoded and deserialized object - * @throws NullPointerException if encodedObject is null - * @throws java.io.IOException if there is a general error - * @throws ClassNotFoundException if the decoded object is of a - * class that cannot be found by the JVM - * @since 1.5 - */ - public static Object decodeToObject(String encodedObject) - throws java.io.IOException, java.lang.ClassNotFoundException - { - return decodeToObject(encodedObject, NO_OPTIONS, null); - } - - - /** - * Attempts to decode Base64 data and deserialize a Java - * Object within. Returns null if there was an error. - * If loader is not null, it will be the class loader - * used when deserializing. - * - * @param encodedObject The Base64 data to decode - * @param options Various parameters related to decoding - * @param loader Optional class loader to use in deserializing classes. - * @return The decoded and deserialized object - * @throws NullPointerException if encodedObject is null - * @throws java.io.IOException if there is a general error - * @throws ClassNotFoundException if the decoded object is of a - * class that cannot be found by the JVM - * @since 2.3.4 - */ - public static Object decodeToObject( - String encodedObject, int options, final ClassLoader loader) - throws java.io.IOException, java.lang.ClassNotFoundException - { - - // Decode and gunzip if necessary - byte[] objBytes = decode(encodedObject, options); - - java.io.ByteArrayInputStream bais = null; - java.io.ObjectInputStream ois = null; - Object obj = null; - - try - { - bais = new java.io.ByteArrayInputStream(objBytes); - - // If no custom class loader is provided, use Java's builtin OIS. - if (loader == null) - { - ois = new java.io.ObjectInputStream(bais); - } // end if: no loader provided - - // Else make a customized object input stream that uses - // the provided class loader. - else - { - ois = new java.io.ObjectInputStream(bais) - { - @Override - public Class resolveClass(java.io.ObjectStreamClass streamClass) - throws java.io.IOException, ClassNotFoundException - { - Class c = Class.forName(streamClass.getName(), false, loader); - if (c == null) - { - return super.resolveClass(streamClass); - } - else - { - return c; // Class loader knows of this class. - } // end else: not null - } // end resolveClass - }; // end ois - } // end else: no custom class loader - - obj = ois.readObject(); - } // end try - catch (java.io.IOException e) - { - throw e; // Catch and throw in order to execute finally{} - } // end catch - catch (java.lang.ClassNotFoundException e) - { - throw e; // Catch and throw in order to execute finally{} - } // end catch - finally - { - try - { bais.close(); } - catch (Exception e) - {} - try - { ois.close(); } - catch (Exception e) - {} - } // end finally - - return obj; - } // end decodeObject - - - /** - * Convenience method for encoding data to a file. - *

- *

As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param dataToEncode byte array of data to encode in base64 form - * @param filename Filename for saving encoded data - * @throws java.io.IOException if there is an error - * @throws NullPointerException if dataToEncode is null - * @since 2.1 - */ - public static void encodeToFile(byte[] dataToEncode, String filename) - throws java.io.IOException - { - - if (dataToEncode == null) - { - throw new NullPointerException("Data to encode was null."); - } // end iff - - Base64.OutputStream bos = null; - try - { - bos = new Base64.OutputStream( - new java.io.FileOutputStream(filename), Base64.ENCODE); - bos.write(dataToEncode); - } // end try - catch (java.io.IOException e) - { - throw e; // Catch and throw to execute finally{} block - } // end catch: java.io.IOException - finally - { - try - { bos.close(); } - catch (Exception e) - {} - } // end finally - - } // end encodeToFile - - - /** - * Convenience method for decoding data to a file. - *

- *

As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param dataToDecode Base64-encoded data as a string - * @param filename Filename for saving decoded data - * @throws java.io.IOException if there is an error - * @since 2.1 - */ - public static void decodeToFile(String dataToDecode, String filename) - throws java.io.IOException - { - - Base64.OutputStream bos = null; - try - { - bos = new Base64.OutputStream( - new java.io.FileOutputStream(filename), Base64.DECODE); - bos.write(dataToDecode.getBytes(PREFERRED_ENCODING)); - } // end try - catch (java.io.IOException e) - { - throw e; // Catch and throw to execute finally{} block - } // end catch: java.io.IOException - finally - { - try - { bos.close(); } - catch (Exception e) - {} - } // end finally - - } // end decodeToFile - - - /** - * Convenience method for reading a base64-encoded - * file and decoding it. - *

- *

As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param filename Filename for reading encoded data - * @return decoded byte array - * @throws java.io.IOException if there is an error - * @since 2.1 - */ - public static byte[] decodeFromFile(String filename) - throws java.io.IOException - { - - byte[] decodedData = null; - Base64.InputStream bis = null; - try - { - // Set up some useful variables - java.io.File file = new java.io.File(filename); - byte[] buffer = null; - int length = 0; - int numBytes = 0; - - // Check for size of file - if (file.length() > Integer.MAX_VALUE) - { - throw new java.io.IOException("File is too big for this convenience method (" + file.length() + " bytes)."); - } // end if: file too big for int index - buffer = new byte[(int) file.length()]; - - // Open a stream - bis = new Base64.InputStream( - new java.io.BufferedInputStream( - new java.io.FileInputStream(file)), Base64.DECODE); - - // Read until done - while ((numBytes = bis.read(buffer, length, 4096)) >= 0) - { - length += numBytes; - } // end while - - // Save in a variable to return - decodedData = new byte[length]; - System.arraycopy(buffer, 0, decodedData, 0, length); - - } // end try - catch (java.io.IOException e) - { - throw e; // Catch and release to execute finally{} - } // end catch: java.io.IOException - finally - { - try - { bis.close(); } - catch (Exception e) - {} - } // end finally - - return decodedData; - } // end decodeFromFile - - - /** - * Convenience method for reading a binary file - * and base64-encoding it. - *

- *

As of v 2.3, if there is a error, - * the method will throw an java.io.IOException. This is new to v2.3! - * In earlier versions, it just returned false, but - * in retrospect that's a pretty poor way to handle it.

- * - * @param filename Filename for reading binary data - * @return base64-encoded string - * @throws java.io.IOException if there is an error - * @since 2.1 - */ - public static String encodeFromFile(String filename) - throws java.io.IOException - { - - String encodedData = null; - Base64.InputStream bis = null; - try - { - // Set up some useful variables - java.io.File file = new java.io.File(filename); - byte[] buffer = new byte[Math.max((int) (file.length() * 1.4 + 1), 40)]; // Need max() for math on small files (v2.2.1); Need +1 for a few corner cases (v2.3.5) - int length = 0; - int numBytes = 0; - - // Open a stream - bis = new Base64.InputStream( - new java.io.BufferedInputStream( - new java.io.FileInputStream(file)), Base64.ENCODE); - - // Read until done - while ((numBytes = bis.read(buffer, length, 4096)) >= 0) - { - length += numBytes; - } // end while - - // Save in a variable to return - encodedData = new String(buffer, 0, length, Base64.PREFERRED_ENCODING); - - } // end try - catch (java.io.IOException e) - { - throw e; // Catch and release to execute finally{} - } // end catch: java.io.IOException - finally - { - try - { bis.close(); } - catch (Exception e) - {} - } // end finally - - return encodedData; - } // end encodeFromFile - - /** - * Reads infile and encodes it to outfile. - * - * @param infile Input file - * @param outfile Output file - * @throws java.io.IOException if there is an error - * @since 2.2 - */ - public static void encodeFileToFile(String infile, String outfile) - throws java.io.IOException - { - - String encoded = Base64.encodeFromFile(infile); - java.io.OutputStream out = null; - try - { - out = new java.io.BufferedOutputStream( - new java.io.FileOutputStream(outfile)); - out.write(encoded.getBytes("US-ASCII")); // Strict, 7-bit output. - } // end try - catch (java.io.IOException e) - { - throw e; // Catch and release to execute finally{} - } // end catch - finally - { - try - { out.close(); } - catch (Exception ex) - {} - } // end finally - } // end encodeFileToFile - - - /** - * Reads infile and decodes it to outfile. - * - * @param infile Input file - * @param outfile Output file - * @throws java.io.IOException if there is an error - * @since 2.2 - */ - public static void decodeFileToFile(String infile, String outfile) - throws java.io.IOException - { - - byte[] decoded = Base64.decodeFromFile(infile); - java.io.OutputStream out = null; - try - { - out = new java.io.BufferedOutputStream( - new java.io.FileOutputStream(outfile)); - out.write(decoded); - } // end try - catch (java.io.IOException e) - { - throw e; // Catch and release to execute finally{} - } // end catch - finally - { - try - { out.close(); } - catch (Exception ex) - {} - } // end finally - } // end decodeFileToFile - - - /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ - - - /** - * A {@link Base64.InputStream} will read data from another - * java.io.InputStream, given in the constructor, - * and encode/decode to/from Base64 notation on the fly. - * - * @see Base64 - * @since 1.3 - */ - public static class InputStream extends java.io.FilterInputStream - { - - private boolean encode; // Encoding or decoding - private int position; // Current position in the buffer - private byte[] buffer; // Small buffer holding converted data - private int bufferLength; // Length of buffer (3 or 4) - private int numSigBytes; // Number of meaningful bytes in the buffer - private int lineLength; - private boolean breakLines; // Break lines at less than 80 characters - private int options; // Record options used to create the stream. - private byte[] decodabet; // Local copies to avoid extra method calls - - - /** - * Constructs a {@link Base64.InputStream} in DECODE mode. - * - * @param in the java.io.InputStream from which to read data. - * @since 1.3 - */ - public InputStream(java.io.InputStream in) - { - this(in, DECODE); - } // end constructor - - - /** - * Constructs a {@link Base64.InputStream} in - * either ENCODE or DECODE mode. - *

- * Valid options:

-         *   ENCODE or DECODE: Encode or Decode as data is read.
-         *   DO_BREAK_LINES: break lines at 76 characters
-         *     (only meaningful when encoding)
-         * 
- *

- * Example: new Base64.InputStream( in, Base64.DECODE ) - * - * @param in the java.io.InputStream from which to read data. - * @param options Specified options - * @see Base64#ENCODE - * @see Base64#DECODE - * @see Base64#DO_BREAK_LINES - * @since 2.0 - */ - public InputStream(java.io.InputStream in, int options) - { - - super(in); - this.options = options; // Record for later - this.breakLines = (options & DO_BREAK_LINES) > 0; - this.encode = (options & ENCODE) > 0; - this.bufferLength = encode ? 4 : 3; - this.buffer = new byte[bufferLength]; - this.position = -1; - this.lineLength = 0; - this.decodabet = getDecodabet(options); - } // end constructor - - /** - * Reads enough of the input stream to convert - * to/from Base64 and returns the next byte. - * - * @return next byte - * @since 1.3 - */ - @Override - public int read() throws java.io.IOException - { - - // Do we need to get data? - if (position < 0) - { - if (encode) - { - byte[] b3 = new byte[3]; - int numBinaryBytes = 0; - for (int i = 0; i < 3; i++) - { - int b = in.read(); - - // If end of stream, b is -1. - if (b >= 0) - { - b3[i] = (byte) b; - numBinaryBytes++; - } - else - { - break; // out of for loop - } // end else: end of stream - - } // end for: each needed input byte - - if (numBinaryBytes > 0) - { - encode3to4(b3, 0, numBinaryBytes, buffer, 0, options); - position = 0; - numSigBytes = 4; - } // end if: got data - else - { - return -1; // Must be end of stream - } // end else - } // end if: encoding - - // Else decoding - else - { - byte[] b4 = new byte[4]; - int i = 0; - for (i = 0; i < 4; i++) - { - // Read four "meaningful" bytes: - int b = 0; - do - { b = in.read(); } - while (b >= 0 && decodabet[b & 0x7f] <= WHITE_SPACE_ENC); - - if (b < 0) - { - break; // Reads a -1 if end of stream - } // end if: end of stream - - b4[i] = (byte) b; - } // end for: each needed input byte - - if (i == 4) - { - numSigBytes = decode4to3(b4, 0, buffer, 0, options); - position = 0; - } // end if: got four characters - else if (i == 0) - { - return -1; - } // end else if: also padded correctly - else - { - // Must have broken out from above. - throw new java.io.IOException("Improperly padded Base64 input."); - } // end - - } // end else: decode - } // end else: get data - - // Got data? - if (position >= 0) - { - // End of relevant data? - if ( /*!encode &&*/ position >= numSigBytes) - { - return -1; - } // end if: got data - - if (encode && breakLines && lineLength >= MAX_LINE_LENGTH) - { - lineLength = 0; - return '\n'; - } // end if - else - { - lineLength++; // This isn't important when decoding - // but throwing an extra "if" seems - // just as wasteful. - - int b = buffer[position++]; - - if (position >= bufferLength) - { - position = -1; - } // end if: end - - return b & 0xFF; // This is how you "cast" a byte that's - // intended to be unsigned. - } // end else - } // end if: position >= 0 - - // Else error - else - { - throw new java.io.IOException("Error in Base64 code reading stream."); - } // end else - } // end read - - - /** - * Calls {@link #read()} repeatedly until the end of stream - * is reached or len bytes are read. - * Returns number of bytes read into array or -1 if - * end of stream is encountered. - * - * @param dest array to hold values - * @param off offset for array - * @param len max number of bytes to read into array - * @return bytes read into array or -1 if end of stream is encountered. - * @since 1.3 - */ - @Override - public int read(byte[] dest, int off, int len) - throws java.io.IOException - { - int i; - int b; - for (i = 0; i < len; i++) - { - b = read(); - - if (b >= 0) - { - dest[off + i] = (byte) b; - } - else if (i == 0) - { - return -1; - } - else - { - break; // Out of 'for' loop - } // Out of 'for' loop - } // end for: each byte read - return i; - } // end read - - } // end inner class InputStream - - - /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ - - - /** - * A {@link Base64.OutputStream} will write data to another - * java.io.OutputStream, given in the constructor, - * and encode/decode to/from Base64 notation on the fly. - * - * @see Base64 - * @since 1.3 - */ - public static class OutputStream extends java.io.FilterOutputStream - { - - private boolean encode; - private int position; - private byte[] buffer; - private int bufferLength; - private int lineLength; - private boolean breakLines; - private byte[] b4; // Scratch used in a few places - private boolean suspendEncoding; - private int options; // Record for later - private byte[] decodabet; // Local copies to avoid extra method calls - - /** - * Constructs a {@link Base64.OutputStream} in ENCODE mode. - * - * @param out the java.io.OutputStream to which data will be written. - * @since 1.3 - */ - public OutputStream(java.io.OutputStream out) - { - this(out, ENCODE); - } // end constructor - - - /** - * Constructs a {@link Base64.OutputStream} in - * either ENCODE or DECODE mode. - *

- * Valid options:

-         *   ENCODE or DECODE: Encode or Decode as data is read.
-         *   DO_BREAK_LINES: don't break lines at 76 characters
-         *     (only meaningful when encoding)
-         * 
- *

- * Example: new Base64.OutputStream( out, Base64.ENCODE ) - * - * @param out the java.io.OutputStream to which data will be written. - * @param options Specified options. - * @see Base64#ENCODE - * @see Base64#DECODE - * @see Base64#DO_BREAK_LINES - * @since 1.3 - */ - public OutputStream(java.io.OutputStream out, int options) - { - super(out); - this.breakLines = (options & DO_BREAK_LINES) != 0; - this.encode = (options & ENCODE) != 0; - this.bufferLength = encode ? 3 : 4; - this.buffer = new byte[bufferLength]; - this.position = 0; - this.lineLength = 0; - this.suspendEncoding = false; - this.b4 = new byte[4]; - this.options = options; - this.decodabet = getDecodabet(options); - } // end constructor - - - /** - * Writes the byte to the output stream after - * converting to/from Base64 notation. - * When encoding, bytes are buffered three - * at a time before the output stream actually - * gets a write() call. - * When decoding, bytes are buffered four - * at a time. - * - * @param theByte the byte to write - * @since 1.3 - */ - @Override - public void write(int theByte) - throws java.io.IOException - { - // Encoding suspended? - if (suspendEncoding) - { - this.out.write(theByte); - return; - } // end if: supsended - - // Encode? - if (encode) - { - buffer[position++] = (byte) theByte; - if (position >= bufferLength) - { // Enough to encode. - - this.out.write(encode3to4(b4, buffer, bufferLength, options)); - - lineLength += 4; - if (breakLines && lineLength >= MAX_LINE_LENGTH) - { - this.out.write(NEW_LINE); - lineLength = 0; - } // end if: end of line - - position = 0; - } // end if: enough to output - } // end if: encoding - - // Else, Decoding - else - { - // Meaningful Base64 character? - if (decodabet[theByte & 0x7f] > WHITE_SPACE_ENC) - { - buffer[position++] = (byte) theByte; - if (position >= bufferLength) - { // Enough to output. - - int len = Base64.decode4to3(buffer, 0, b4, 0, options); - out.write(b4, 0, len); - position = 0; - } // end if: enough to output - } // end if: meaningful base64 character - else if (decodabet[theByte & 0x7f] != WHITE_SPACE_ENC) - { - throw new java.io.IOException("Invalid character in Base64 data."); - } // end else: not white space either - } // end else: decoding - } // end write - - - /** - * Calls {@link #write(int)} repeatedly until len - * bytes are written. - * - * @param theBytes array from which to read bytes - * @param off offset for array - * @param len max number of bytes to read into array - * @since 1.3 - */ - @Override - public void write(byte[] theBytes, int off, int len) - throws java.io.IOException - { - // Encoding suspended? - if (suspendEncoding) - { - this.out.write(theBytes, off, len); - return; - } // end if: supsended - - for (int i = 0; i < len; i++) - { - write(theBytes[off + i]); - } // end for: each byte written - - } // end write - - - /** - * Method added by PHIL. [Thanks, PHIL. -Rob] - * This pads the buffer without closing the stream. - * - * @throws java.io.IOException if there's an error. - */ - public void flushBase64() throws java.io.IOException - { - if (position > 0) - { - if (encode) - { - out.write(encode3to4(b4, buffer, position, options)); - position = 0; - } // end if: encoding - else - { - throw new java.io.IOException("Base64 input not properly padded."); - } // end else: decoding - } // end if: buffer partially full - - } // end flush - - - /** - * Flushes and closes (I think, in the superclass) the stream. - * - * @since 1.3 - */ - @Override - public void close() throws java.io.IOException - { - // 1. Ensure that pending characters are written - flushBase64(); - - // 2. Actually close the stream - // Base class both flushes and closes. - super.close(); - - buffer = null; - out = null; - } // end close - - - /** - * Suspends encoding of the stream. - * May be helpful if you need to embed a piece of - * base64-encoded data in a stream. - * - * @throws java.io.IOException if there's an error flushing - * @since 1.5.1 - */ - public void suspendEncoding() throws java.io.IOException - { - flushBase64(); - this.suspendEncoding = true; - } // end suspendEncoding - - - /** - * Resumes encoding of the stream. - * May be helpful if you need to embed a piece of - * base64-encoded data in a stream. - * - * @since 1.5.1 - */ - public void resumeEncoding() - { - this.suspendEncoding = false; - } // end resumeEncoding - - - } // end inner class OutputStream - - -} // end class Base64 diff --git a/model/api/src/main/java/org/keycloak/models/utils/Pbkdf2PasswordEncoder.java b/model/api/src/main/java/org/keycloak/models/utils/Pbkdf2PasswordEncoder.java index b11266ff29..5ca5528d2b 100644 --- a/model/api/src/main/java/org/keycloak/models/utils/Pbkdf2PasswordEncoder.java +++ b/model/api/src/main/java/org/keycloak/models/utils/Pbkdf2PasswordEncoder.java @@ -1,5 +1,7 @@ package org.keycloak.models.utils; +import net.iharder.Base64; + import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import java.security.NoSuchAlgorithmException; diff --git a/model/api/src/main/java/org/keycloak/models/utils/SHAPasswordEncoder.java b/model/api/src/main/java/org/keycloak/models/utils/SHAPasswordEncoder.java index a9fec22b37..3a8ed8ddb8 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/SHAPasswordEncoder.java +++ b/model/api/src/main/java/org/keycloak/models/utils/SHAPasswordEncoder.java @@ -1,5 +1,7 @@ package org.keycloak.models.utils; +import net.iharder.Base64; + import java.io.UnsupportedEncodingException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; diff --git a/pom.xml b/pom.xml index 4b9409465b..75712f367a 100755 --- a/pom.xml +++ b/pom.xml @@ -101,6 +101,11 @@ bcmail-jdk16 1.46 + + net.iharder + base64 + 2.3.8 + org.jboss.resteasy jaxrs-api From 2efcb95fd62e68ae9e9863b69d90b91c8d9e601b Mon Sep 17 00:00:00 2001 From: Matthias Wessendorf Date: Tue, 28 Jan 2014 14:30:22 +0100 Subject: [PATCH 2/6] some minor util class clean-up, to prevent instanciation of util classes --- core/src/main/java/org/keycloak/util/DerUtils.java | 5 ++++- core/src/main/java/org/keycloak/util/EnvUtil.java | 6 +++++- core/src/main/java/org/keycloak/util/PemUtils.java | 5 ++++- core/src/main/java/org/keycloak/util/StreamUtil.java | 6 +++++- .../org/keycloak/models/utils/KeycloakSessionUtils.java | 5 ++++- .../org/keycloak/testsuite/performance/PerfTestUtils.java | 5 ++++- 6 files changed, 26 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/keycloak/util/DerUtils.java b/core/src/main/java/org/keycloak/util/DerUtils.java index 8e1a889987..a3aa745a45 100755 --- a/core/src/main/java/org/keycloak/util/DerUtils.java +++ b/core/src/main/java/org/keycloak/util/DerUtils.java @@ -20,11 +20,14 @@ import java.security.spec.X509EncodedKeySpec; * @author Bill Burke * @version $Revision: 1 $ */ -public class DerUtils { +public final class DerUtils { static { BouncyIntegration.init(); } + private DerUtils() { + } + public static PrivateKey decodePrivateKey(InputStream is) throws Exception { diff --git a/core/src/main/java/org/keycloak/util/EnvUtil.java b/core/src/main/java/org/keycloak/util/EnvUtil.java index 3dbc87853b..eafc7f6fa1 100755 --- a/core/src/main/java/org/keycloak/util/EnvUtil.java +++ b/core/src/main/java/org/keycloak/util/EnvUtil.java @@ -7,9 +7,13 @@ import java.util.regex.Pattern; * @author Bill Burke * @version $Revision: 1 $ */ -public class EnvUtil { +public final class EnvUtil { private static final Pattern p = Pattern.compile("[$][{]([^}]+)[}]"); + private EnvUtil() { + + } + /** * Replaces any ${} strings with their corresponding environent variable. * diff --git a/core/src/main/java/org/keycloak/util/PemUtils.java b/core/src/main/java/org/keycloak/util/PemUtils.java index 6f8979cbbd..e7a20ec162 100755 --- a/core/src/main/java/org/keycloak/util/PemUtils.java +++ b/core/src/main/java/org/keycloak/util/PemUtils.java @@ -15,11 +15,14 @@ import java.security.cert.X509Certificate; * @author Bill Burke * @version $Revision: 1 $ */ -public class PemUtils { +public final class PemUtils { static { BouncyIntegration.init(); } + private PemUtils() { + } + public static X509Certificate decodeCertificate(InputStream is) throws Exception { byte[] der = pemToDer(is); ByteArrayInputStream bis = new ByteArrayInputStream(der); diff --git a/core/src/main/java/org/keycloak/util/StreamUtil.java b/core/src/main/java/org/keycloak/util/StreamUtil.java index 428a5903d8..67cd8d71a8 100755 --- a/core/src/main/java/org/keycloak/util/StreamUtil.java +++ b/core/src/main/java/org/keycloak/util/StreamUtil.java @@ -9,7 +9,11 @@ import java.io.InputStreamReader; * @author Bill Burke * @version $Revision: 1 $ */ -public class StreamUtil { +public final class StreamUtil { + + private StreamUtil() { + } + public static String readString(InputStream in) throws IOException { char[] buffer = new char[1024]; diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakSessionUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakSessionUtils.java index 9119282b4f..57285db004 100644 --- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakSessionUtils.java +++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakSessionUtils.java @@ -5,7 +5,10 @@ import java.util.concurrent.atomic.AtomicLong; /** * @author Marek Posolda */ -public class KeycloakSessionUtils { +public final class KeycloakSessionUtils { + + private KeycloakSessionUtils() { + } private static AtomicLong counter = new AtomicLong(1); diff --git a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/PerfTestUtils.java b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/PerfTestUtils.java index 75b355459a..d59ccf519b 100644 --- a/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/PerfTestUtils.java +++ b/testsuite/performance/src/test/java/org/keycloak/testsuite/performance/PerfTestUtils.java @@ -3,7 +3,10 @@ package org.keycloak.testsuite.performance; /** * @author Marek Posolda */ -public class PerfTestUtils { +public final class PerfTestUtils { + + private PerfTestUtils() { + } public static T readSystemProperty(String propertyName, Class expectedClass) { String propAsString = System.getProperty(propertyName); From c81a08385fc987a039dc1bd2202642694a6244b8 Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Wed, 29 Jan 2014 18:52:31 -0500 Subject: [PATCH 3/6] KEYCLOAK-274 Create Keycloak subsystem --- .../keycloak-subsystem/main/module.xml | 42 ++++ .../undertow/KeycloakServletExtension.java | 18 +- pom.xml | 42 +++- subsystem/pom.xml | 115 +++++++++ .../extension/CredentialAddHandler.java | 46 ++++ .../extension/CredentialDefinition.java | 63 +++++ .../CredentialReadWriteAttributeHandler.java | 50 ++++ .../extension/CredentialRemoveHandler.java | 42 ++++ ...cloakAdapterConfigDeploymentProcessor.java | 112 +++++++++ .../KeycloakAdapterConfigService.java | 207 ++++++++++++++++ .../KeycloakDependencyProcessor.java | 65 +++++ .../extension/KeycloakExtension.java | 85 +++++++ .../extension/KeycloakSubsystemAdd.java | 75 ++++++ .../KeycloakSubsystemDefinition.java | 47 ++++ .../extension/KeycloakSubsystemParser.java | 227 ++++++++++++++++++ .../subsystem/extension/RealmAddHandler.java | 65 +++++ .../subsystem/extension/RealmDefinition.java | 173 +++++++++++++ .../extension/RealmRemoveHandler.java | 41 ++++ .../extension/RealmWriteAttributeHandler.java | 59 +++++ .../extension/SecureDeploymentAddHandler.java | 60 +++++ .../extension/SecureDeploymentDefinition.java | 108 +++++++++ .../SecureDeploymentRemoveHandler.java | 41 ++++ ...SecureDeploymentWriteAttributeHandler.java | 58 +++++ .../extension/SharedAttributeDefinitons.java | 97 ++++++++ .../subsystem/logging/KeycloakLogger.java | 67 ++++++ .../subsystem/logging/KeycloakMessages.java | 61 +++++ .../org.jboss.as.controller.Extension | 1 + .../extension/LocalDescriptions.properties | 49 ++++ .../main/resources/schema/keycloak_1_0.xsd | 89 +++++++ .../extension/RealmDefinitionTestCase.java | 79 ++++++ .../extension/SubsystemParsingTestCase.java | 175 ++++++++++++++ 31 files changed, 2454 insertions(+), 5 deletions(-) create mode 100644 distribution/modules/src/main/resources/modules/org/keycloak/keycloak-subsystem/main/module.xml create mode 100644 subsystem/pom.xml create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialAddHandler.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialDefinition.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialReadWriteAttributeHandler.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialRemoveHandler.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakDependencyProcessor.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakExtension.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemAdd.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemDefinition.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemParser.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/RealmAddHandler.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/RealmDefinition.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/RealmRemoveHandler.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/RealmWriteAttributeHandler.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentAddHandler.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentDefinition.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentRemoveHandler.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentWriteAttributeHandler.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/extension/SharedAttributeDefinitons.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakLogger.java create mode 100644 subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakMessages.java create mode 100644 subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension create mode 100644 subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties create mode 100644 subsystem/src/main/resources/schema/keycloak_1_0.xsd create mode 100644 subsystem/src/test/java/org/keycloak/subsystem/extension/RealmDefinitionTestCase.java create mode 100644 subsystem/src/test/java/org/keycloak/subsystem/extension/SubsystemParsingTestCase.java diff --git a/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-subsystem/main/module.xml b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-subsystem/main/module.xml new file mode 100644 index 0000000000..06e1229a40 --- /dev/null +++ b/distribution/modules/src/main/resources/modules/org/keycloak/keycloak-subsystem/main/module.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java index 52eb239bdd..6be2346f59 100755 --- a/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java +++ b/integration/undertow/src/main/java/org/keycloak/adapters/undertow/KeycloakServletExtension.java @@ -11,6 +11,7 @@ import io.undertow.servlet.api.AuthMethodConfig; import io.undertow.servlet.api.DeploymentInfo; import io.undertow.servlet.api.LoginConfig; import io.undertow.servlet.api.ServletSessionConfig; +import java.io.ByteArrayInputStream; import org.jboss.logging.Logger; import org.keycloak.adapters.config.RealmConfiguration; import org.keycloak.representations.adapters.config.AdapterConfig; @@ -25,6 +26,11 @@ import java.util.Map; * @version $Revision: 1 $ */ public class KeycloakServletExtension implements ServletExtension { + // This param name is defined again in Keycloak Subsystem class + // org.keycloak.subsystem.extensionKeycloakAdapterConfigDeploymentProcessor. We have this value in + // two places to avoid dependency between Keycloak Subsystem and Keyclaok Undertow Integration. + public static final String AUTH_DATA_PARAM_NAME = "org.keycloak.json.adapterConfig"; + protected Logger log = Logger.getLogger(KeycloakServletExtension.class); // todo when this DeploymentInfo method of the same name is fixed. @@ -40,6 +46,13 @@ public class KeycloakServletExtension implements ServletExtension { return false; } + private InputStream getJSONFromServletContext(ServletContext servletContext) { + String json = servletContext.getInitParameter(AUTH_DATA_PARAM_NAME); + if (json == null) { + return null; + } + return new ByteArrayInputStream(json.getBytes()); + } @Override public void handleDeployment(DeploymentInfo deploymentInfo, ServletContext servletContext) { @@ -49,7 +62,10 @@ public class KeycloakServletExtension implements ServletExtension { } log.info("KeycloakServletException initialization"); InputStream is = servletContext.getResourceAsStream("/WEB-INF/keycloak.json"); - if (is == null) throw new RuntimeException("Unable to find /WEB-INF/keycloak.json configuration file"); + if (is == null) { + is = getJSONFromServletContext(servletContext); + } + if (is == null) throw new RuntimeException("Unable to find realm config in /WEB-INF/keycloak.json or in keycloak subsystem."); RealmConfigurationLoader loader = new RealmConfigurationLoader(is); loader.init(true); AdapterConfig keycloakConfig = loader.getAdapterConfig(); diff --git a/pom.xml b/pom.xml index 4b9409465b..533f92f4a5 100755 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,7 @@ 2.5.0.Beta6 2.11.2 3.1.1.GA + 1.2.0.Beta1 1.0.1.Final 4.0.1.Final 1.3.161 @@ -87,6 +88,7 @@ examples testsuite server + subsystem @@ -252,7 +254,7 @@ json ${json.version} - + com.google.http-client @@ -264,14 +266,14 @@ google-api-services-oauth2 v2-rev35-1.14.1-beta - + org.twitter4j twitter4j-core 3.0.5 - + com.google.zxing @@ -283,7 +285,7 @@ javase 2.2 - + com.icegreen @@ -340,6 +342,38 @@ ${keycloak.apache.httpcomponents.version} --> + + org.wildfly + wildfly-controller + ${wildfly.version} + + + org.wildfly + wildfly-server + ${wildfly.version} + + + org.wildfly + wildfly-ee + ${wildfly.version} + + + org.wildfly + wildfly-subsystem-test + ${wildfly.version} + pom + test + + + org.wildfly + wildfly-undertow + ${wildfly.version} + + + org.jboss.logging + jboss-logging-processor + ${jboss-logging-tools.version} + diff --git a/subsystem/pom.xml b/subsystem/pom.xml new file mode 100644 index 0000000000..d976b175d0 --- /dev/null +++ b/subsystem/pom.xml @@ -0,0 +1,115 @@ + + + + 4.0.0 + + + org.keycloak + keycloak-parent + 1.0-alpha-2-SNAPSHOT + + + org.keycloak + keycloak-subsystem + 1.0-alpha-2-SNAPSHOT + + Keycloak Subsystem + + jar + + + + + maven-compiler-plugin + 2.3.1 + + 1.6 + 1.6 + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.8.1 + + false + true + -Xmx512m + + + jboss.home + ${jboss.home} + + + + **/*TestCase.java + + once + + + + + + + + org.wildfly + wildfly-controller + + + org.wildfly + wildfly-server + + + org.wildfly + wildfly-ee + + + org.wildfly + wildfly-undertow + + + org.jboss.logging + jboss-logging-annotations + ${jboss-logging-tools.version} + + provided + true + + + + org.jboss.logging + jboss-logging-processor + + provided + true + + + + org.wildfly + wildfly-subsystem-test + pom + test + + + junit + junit + test + + + diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialAddHandler.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialAddHandler.java new file mode 100644 index 0000000000..ea14dfb4b8 --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialAddHandler.java @@ -0,0 +1,46 @@ +/* + * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import java.util.List; +import org.jboss.as.controller.AbstractAddStepHandler; +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.ServiceVerificationHandler; +import org.jboss.dmr.ModelNode; +import org.jboss.msc.service.ServiceController; + +/** + * Add a credential to a deployment. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc. + */ +public class CredentialAddHandler extends AbstractAddStepHandler { + + public CredentialAddHandler(AttributeDefinition... attributes) { + super(attributes); + } + + @Override + protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List> newControllers) throws OperationFailedException { + KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.find(context); + ckService.addCredential(operation, model); + } + +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialDefinition.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialDefinition.java new file mode 100644 index 0000000000..9ae3abdfb6 --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialDefinition.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.ModelOnlyWriteAttributeHandler; +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; +import org.jboss.as.controller.SimpleResourceDefinition; +import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler; +import org.jboss.as.controller.operations.validation.StringLengthValidator; +import org.jboss.as.controller.registry.ManagementResourceRegistration; +import org.jboss.dmr.ModelType; + +/** + * Defines attributes and operations for a credential. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class CredentialDefinition extends SimpleResourceDefinition { + + public static final String TAG_NAME = "credential"; + + protected static final AttributeDefinition VALUE = + new SimpleAttributeDefinitionBuilder("value", ModelType.STRING, false) + .setXmlName("value") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, false, true)) + .build(); + + public CredentialDefinition() { + super(PathElement.pathElement(TAG_NAME), + KeycloakExtension.getResourceDescriptionResolver(TAG_NAME), + new CredentialAddHandler(VALUE), + CredentialRemoveHandler.INSTANCE); + } + + @Override + public void registerOperations(ManagementResourceRegistration resourceRegistration) { + super.registerOperations(resourceRegistration); + resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE); + } + + @Override + public void registerAttributes(ManagementResourceRegistration resourceRegistration) { + super.registerAttributes(resourceRegistration); + resourceRegistration.registerReadWriteAttribute(VALUE, null, new CredentialReadWriteAttributeHandler()); + } +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialReadWriteAttributeHandler.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialReadWriteAttributeHandler.java new file mode 100644 index 0000000000..6289ff4539 --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialReadWriteAttributeHandler.java @@ -0,0 +1,50 @@ +/* + * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import org.jboss.as.controller.AbstractWriteAttributeHandler; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.dmr.ModelNode; + +/** + * Update a credential value. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc. + */ +public class CredentialReadWriteAttributeHandler extends AbstractWriteAttributeHandler { + + @Override + protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, + ModelNode resolvedValue, ModelNode currentValue, AbstractWriteAttributeHandler.HandbackHolder hh) throws OperationFailedException { + + KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.find(context); + ckService.updateCredential(operation, attributeName, resolvedValue); + + hh.setHandback(ckService); + + return false; + } + + @Override + protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, + ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException { + ckService.updateCredential(operation, attributeName, valueToRestore); + } + +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialRemoveHandler.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialRemoveHandler.java new file mode 100644 index 0000000000..9d1d698df8 --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/CredentialRemoveHandler.java @@ -0,0 +1,42 @@ +/* + * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import org.jboss.as.controller.AbstractRemoveStepHandler; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.dmr.ModelNode; + +/** + * Remove a credential from a deployment. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc. + */ +public class CredentialRemoveHandler extends AbstractRemoveStepHandler { + + public static CredentialRemoveHandler INSTANCE = new CredentialRemoveHandler(); + + private CredentialRemoveHandler() {} + + @Override + protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException { + KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.find(context); + ckService.removeCredential(operation); + } + +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java new file mode 100644 index 0000000000..17d73841d7 --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java @@ -0,0 +1,112 @@ +/* + * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import java.util.ArrayList; +import java.util.List; +import org.jboss.as.server.deployment.DeploymentPhaseContext; +import org.jboss.as.server.deployment.DeploymentUnit; +import org.jboss.as.server.deployment.DeploymentUnitProcessingException; +import org.jboss.as.server.deployment.DeploymentUnitProcessor; +import org.jboss.as.server.deployment.Phase; +import org.jboss.as.web.common.WarMetaData; +import org.jboss.metadata.javaee.spec.ParamValueMetaData; +import org.jboss.metadata.web.jboss.JBossWebMetaData; + +/** + * Pass authentication data (keycloak.json) as a servlet context param so it can be read by the KeycloakServletExtension. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2014 Red Hat Inc. + */ +public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitProcessor { + // This param name is defined again in Keycloak Undertow Integration class + // org.keycloak.adapters.undertow.KeycloakServletExtension. We have this value in + // two places to avoid dependency between Keycloak Subsystem and Keyclaok Undertow Integration. + public static final String AUTH_DATA_PARAM_NAME = "org.keycloak.json.adapterConfig"; + + public static final Phase PHASE = Phase.INSTALL; + // Seems wise to have this run after INSTALL_WAR_DEPLOYMENT + public static final int PRIORITY = Phase.INSTALL_WAR_DEPLOYMENT + 1; + + @Override + public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException { + DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit(); + String deploymentName = deploymentUnit.getName(); + + KeycloakAdapterConfigService service = KeycloakAdapterConfigService.find(phaseContext.getServiceRegistry()); + if (service.isKeycloakDeployment(deploymentName)) { + addKeycloakAuthData(phaseContext, deploymentName, service); + } + } + + private void addKeycloakAuthData(DeploymentPhaseContext phaseContext, String deploymentName, KeycloakAdapterConfigService service) { + DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit(); + WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY); + + //TODO: Find context root properly + String resourceName = deploymentName.substring(0, deploymentName.lastIndexOf('.')); + + addJSONData(service.getJSON(deploymentName, resourceName), warMetaData); + //addJSONData(getJSON(), warMetaData); + } + + // TODO: remove this. + private String getJSON() { + return "{\n" + +" \"realm\": \"demo\",\n" + +" \"realm-public-key\": \"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB\",\n" + +" \"auth-server-url\": \"http://localhost:8080/auth\",\n" + +" \"ssl-not-required\": true,\n" + +" \"resource\": \"customer-portal-subsys\",\n" + +" \"credentials\": {\n" + +" \"password\": \"password\"\n" + +" },\n" + +" \"use-resource-role-mappings\": false,\n" + +" \"enable-cors\": false,\n" + +" \"cors-max-age\": -1,\n" + +" \"expose-token\": false,\n" + +" \"bearer-only\": false\n" + +"}"; + } + + private void addJSONData(String json, WarMetaData warMetaData) { + JBossWebMetaData webMetaData = warMetaData.getMergedJBossWebMetaData(); + if (webMetaData == null) { + webMetaData = new JBossWebMetaData(); + warMetaData.setMergedJBossWebMetaData(webMetaData); + } + + List contextParams = webMetaData.getContextParams(); + if (contextParams == null) { + contextParams = new ArrayList(); + } + + ParamValueMetaData param = new ParamValueMetaData(); + param.setParamName(AUTH_DATA_PARAM_NAME); + param.setParamValue(json); + contextParams.add(param); + + webMetaData.setContextParams(contextParams); + } + + @Override + public void undeploy(DeploymentUnit du) { + + } + +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java new file mode 100644 index 0000000000..11f71b2dac --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java @@ -0,0 +1,207 @@ +/* + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import java.util.HashMap; +import java.util.Map; +import org.jboss.as.controller.OperationContext; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.Property; +import org.jboss.msc.service.Service; +import org.jboss.msc.service.ServiceController; +import org.jboss.msc.service.ServiceName; +import org.jboss.msc.service.ServiceRegistry; +import org.jboss.msc.service.StartContext; +import org.jboss.msc.service.StartException; +import org.jboss.msc.service.StopContext; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS; + +/** + * This service keeps track of the entire Keycloak management model so as to provide + * adapter configuration to each deployment at deploy time. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class KeycloakAdapterConfigService implements Service { + private static final String CREDENTIALS_JSON_NAME = "credentials"; + + // Right now this is used as a service, but I'm not sure it really needs to be implemented that way. + // It's also a singleton serving the entire subsystem, but the INSTANCE variable is currently only + // used during initialization of the subsystem. + public static final ServiceName SERVICE_NAME = ServiceName.JBOSS.append("KeycloakAdapterConfigService"); + public static final KeycloakAdapterConfigService INSTANCE = new KeycloakAdapterConfigService(); + + private Map realms = new HashMap(); + private Map deployments = new HashMap(); + + private KeycloakAdapterConfigService() { + + } + + @Override + public void start(StartContext sc) throws StartException { + + } + + @Override + public void stop(StopContext sc) { + + } + + @Override + public KeycloakAdapterConfigService getValue() throws IllegalStateException, IllegalArgumentException { + return this; + } + + public void addRealm(ModelNode operation, ModelNode model) { + this.realms.put(realmNameFromOp(operation), model.clone()); + } + + public void updateRealm(ModelNode operation, String attrName, ModelNode resolvedValue) { + ModelNode realm = this.realms.get(realmNameFromOp(operation)); + realm.get(attrName).set(resolvedValue); + } + + public void removeRealm(ModelNode operation) { + this.realms.remove(realmNameFromOp(operation)); + } + + public void addSecureDeployment(ModelNode operation, ModelNode model) { + ModelNode deployment = model.clone(); + deployment.get(RealmDefinition.TAG_NAME).set(realmNameFromOp(operation)); + this.deployments.put(deploymentNameFromOp(operation), deployment); + } + + public void updateSecureDeployment(ModelNode operation, String attrName, ModelNode resolvedValue) { + ModelNode deployment = this.deployments.get(deploymentNameFromOp(operation)); + deployment.get(attrName).set(resolvedValue); + } + + public void removeSecureDeployment(ModelNode operation) { + this.deployments.remove(deploymentNameFromOp(operation)); + } + + public void addCredential(ModelNode operation, ModelNode model) { + ModelNode credentials = credentialsFromOp(operation); + if (!credentials.isDefined()) { + credentials = new ModelNode(); + } + + String credentialName = credentialNameFromOp(operation); + credentials.get(credentialName).set(model.get("value").asString()); + + ModelNode deployment = this.deployments.get(deploymentNameFromOp(operation)); + deployment.get(CREDENTIALS_JSON_NAME).set(credentials); + } + + public void removeCredential(ModelNode operation) { + ModelNode credentials = credentialsFromOp(operation); + if (!credentials.isDefined()) { + throw new RuntimeException("Can not remove credential. No credential defined for deployment in op " + operation.toString()); + } + + String credentialName = credentialNameFromOp(operation); + credentials.remove(credentialName); + } + + public void updateCredential(ModelNode operation, String attrName, ModelNode resolvedValue) { + ModelNode credentials = credentialsFromOp(operation); + if (!credentials.isDefined()) { + throw new RuntimeException("Can not update credential. No credential defined for deployment in op " + operation.toString()); + } + + String credentialName = credentialNameFromOp(operation); + credentials.get(credentialName).set(resolvedValue); + } + + private ModelNode credentialsFromOp(ModelNode operation) { + ModelNode deployment = this.deployments.get(deploymentNameFromOp(operation)); + return deployment.get(CREDENTIALS_JSON_NAME); + } + + private String realmNameFromOp(ModelNode operation) { + return valueFromOpAddress(RealmDefinition.TAG_NAME, operation); + } + + private String deploymentNameFromOp(ModelNode operation) { + return valueFromOpAddress(SecureDeploymentDefinition.TAG_NAME, operation); + } + + private String credentialNameFromOp(ModelNode operation) { + return valueFromOpAddress(CredentialDefinition.TAG_NAME, operation); + } + + private String valueFromOpAddress(String addrElement, ModelNode operation) { + String deploymentName = getValueOfAddrElement(operation.get(ADDRESS), addrElement); + if (deploymentName == null) throw new RuntimeException("Can't find '" + addrElement + "' in address " + operation.toString()); + return deploymentName; + } + + private String getValueOfAddrElement(ModelNode address, String elementName) { + for (ModelNode element : address.asList()) { + if (element.has(elementName)) return element.get(elementName).asString(); + } + + return null; + } + + public String getJSON(String deploymentName, String resourceName) { + ModelNode deployment = this.deployments.get(deploymentName); + String realmName = deployment.get(RealmDefinition.TAG_NAME).asString(); + ModelNode realm = this.realms.get(realmName); + + ModelNode json = new ModelNode(); + json.get(RealmDefinition.TAG_NAME).set(realmName); + json.get("resource").set(resourceName); + + // Realm values set first. Some can be overridden by deployment values. + setJSONValues(json, realm); + setJSONValues(json, deployment); + + // TODO: change this to true to compact the string + return json.toJSONString(false); + } + + private void setJSONValues(ModelNode json, ModelNode values) { + for (Property prop : values.asPropertyList()) { + String name = prop.getName(); + ModelNode value = prop.getValue(); + if (value.isDefined()) { + json.get(name).set(value); + } + } + } + + public boolean isKeycloakDeployment(String deploymentName) { + return this.deployments.containsKey(deploymentName); + } + + static KeycloakAdapterConfigService find(ServiceRegistry registry) { + ServiceController container = registry.getService(KeycloakAdapterConfigService.SERVICE_NAME); + if (container != null) { + KeycloakAdapterConfigService service = (KeycloakAdapterConfigService)container.getValue(); + return service; + } + return null; + } + + static KeycloakAdapterConfigService find(OperationContext context) { + return find(context.getServiceRegistry(true)); + } +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakDependencyProcessor.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakDependencyProcessor.java new file mode 100644 index 0000000000..eb885b02c9 --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakDependencyProcessor.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import org.jboss.as.server.deployment.Attachments; +import org.jboss.as.server.deployment.DeploymentPhaseContext; +import org.jboss.as.server.deployment.DeploymentUnit; +import org.jboss.as.server.deployment.DeploymentUnitProcessingException; +import org.jboss.as.server.deployment.DeploymentUnitProcessor; +import org.jboss.as.server.deployment.module.ModuleDependency; +import org.jboss.as.server.deployment.module.ModuleSpecification; +import org.jboss.modules.Module; +import org.jboss.modules.ModuleIdentifier; +import org.jboss.modules.ModuleLoader; + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class KeycloakDependencyProcessor implements DeploymentUnitProcessor { + + private static final ModuleIdentifier KEYCLOAK_UNDERTOW_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-undertow-adapter"); + private static final ModuleIdentifier KEYCLOAK_CORE_ADAPTER = ModuleIdentifier.create("org.keycloak.keycloak-adapter-core"); + private static final ModuleIdentifier APACHE_HTTPCOMPONENTS = ModuleIdentifier.create("org.apache.httpcomponents"); + + @Override + public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException { + final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit(); + + KeycloakAdapterConfigService service = KeycloakAdapterConfigService.find(phaseContext.getServiceRegistry()); + if (service.isKeycloakDeployment(deploymentUnit.getName())) { + addModules(deploymentUnit); + } + } + + private void addModules(DeploymentUnit deploymentUnit) { + final ModuleSpecification moduleSpecification = deploymentUnit.getAttachment(Attachments.MODULE_SPECIFICATION); + final ModuleLoader moduleLoader = Module.getBootModuleLoader(); + + moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_UNDERTOW_ADAPTER, false, false, true, false)); + moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE_ADAPTER, false, false, false, false)); + moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, APACHE_HTTPCOMPONENTS, false, false, true, false)); + } + + @Override + public void undeploy(DeploymentUnit du) { + + } + +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakExtension.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakExtension.java new file mode 100644 index 0000000000..ed953d1e52 --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakExtension.java @@ -0,0 +1,85 @@ +/* + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import org.jboss.as.controller.Extension; +import org.jboss.as.controller.ExtensionContext; +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.SubsystemRegistration; +import org.jboss.as.controller.descriptions.StandardResourceDescriptionResolver; +import org.jboss.as.controller.parsing.ExtensionParsingContext; +import org.jboss.as.controller.registry.ManagementResourceRegistration; +import org.jboss.as.controller.ResourceDefinition; +import org.keycloak.subsystem.logging.KeycloakLogger; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM; + + +/** + * Main Extension class for the subsystem. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class KeycloakExtension implements Extension { + + public static final String SUBSYSTEM_NAME = "keycloak"; + public static final String NAMESPACE = "urn:jboss:domain:keycloak:1.0"; + private static final KeycloakSubsystemParser PARSER = new KeycloakSubsystemParser(); + static final PathElement PATH_SUBSYSTEM = PathElement.pathElement(SUBSYSTEM, SUBSYSTEM_NAME); + private static final String RESOURCE_NAME = KeycloakExtension.class.getPackage().getName() + ".LocalDescriptions"; + private static final int MANAGEMENT_API_MAJOR_VERSION = 1; + private static final int MANAGEMENT_API_MINOR_VERSION = 0; + private static final int MANAGEMENT_API_MICRO_VERSION = 0; + protected static final PathElement SUBSYSTEM_PATH = PathElement.pathElement(SUBSYSTEM, SUBSYSTEM_NAME); + private static final ResourceDefinition KEYCLOAK_SUBSYSTEM_RESOURCE = new KeycloakSubsystemDefinition(); + static final RealmDefinition REALM_DEFINITION = new RealmDefinition(); + static final SecureDeploymentDefinition SECURE_DEPLOYMENT_DEFINITION = new SecureDeploymentDefinition(); + static final CredentialDefinition CREDENTIAL_DEFINITION = new CredentialDefinition(); + + static StandardResourceDescriptionResolver getResourceDescriptionResolver(final String... keyPrefix) { + StringBuilder prefix = new StringBuilder(SUBSYSTEM_NAME); + for (String kp : keyPrefix) { + prefix.append('.').append(kp); + } + return new StandardResourceDescriptionResolver(prefix.toString(), RESOURCE_NAME, KeycloakExtension.class.getClassLoader(), true, false); + } + + /** + * {@inheritDoc} + */ + @Override + public void initializeParsers(final ExtensionParsingContext context) { + context.setSubsystemXmlMapping(SUBSYSTEM_NAME, KeycloakExtension.NAMESPACE, PARSER); + } + + /** + * {@inheritDoc} + */ + @Override + public void initialize(final ExtensionContext context) { + KeycloakLogger.ROOT_LOGGER.debug("Activating Keycloak Extension"); + final SubsystemRegistration subsystem = context.registerSubsystem(SUBSYSTEM_NAME, MANAGEMENT_API_MAJOR_VERSION, + MANAGEMENT_API_MINOR_VERSION, MANAGEMENT_API_MICRO_VERSION); + + ManagementResourceRegistration registration = subsystem.registerSubsystemModel(KEYCLOAK_SUBSYSTEM_RESOURCE); + ManagementResourceRegistration realmRegistration = registration.registerSubModel(REALM_DEFINITION); + ManagementResourceRegistration secureDeploymentRegistration = realmRegistration.registerSubModel(SECURE_DEPLOYMENT_DEFINITION); + secureDeploymentRegistration.registerSubModel(CREDENTIAL_DEFINITION); + + subsystem.registerXMLElementWriter(PARSER); + } +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemAdd.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemAdd.java new file mode 100644 index 0000000000..846a7701af --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemAdd.java @@ -0,0 +1,75 @@ +/* + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import java.util.List; + +import org.jboss.as.controller.AbstractBoottimeAddStepHandler; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.ServiceVerificationHandler; +import org.jboss.as.server.AbstractDeploymentChainStep; +import org.jboss.as.server.DeploymentProcessorTarget; +import org.jboss.as.server.deployment.Phase; +import org.jboss.dmr.ModelNode; +import org.jboss.msc.service.ServiceController; + +/** + * The Keycloak subsystem add update handler. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler { + + static final KeycloakSubsystemAdd INSTANCE = new KeycloakSubsystemAdd(); + + @Override + protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException { + model.setEmptyObject(); + } + + @Override + protected void performBoottime(final OperationContext context, ModelNode operation, final ModelNode model, ServiceVerificationHandler verificationHandler, List> newControllers) { + context.addStep(new AbstractDeploymentChainStep() { + @Override + protected void execute(DeploymentProcessorTarget processorTarget) { + processorTarget.addDeploymentProcessor(KeycloakExtension.SUBSYSTEM_NAME, Phase.DEPENDENCIES, 0, new KeycloakDependencyProcessor()); + processorTarget.addDeploymentProcessor(KeycloakExtension.SUBSYSTEM_NAME, + KeycloakAdapterConfigDeploymentProcessor.PHASE, + KeycloakAdapterConfigDeploymentProcessor.PRIORITY, + new KeycloakAdapterConfigDeploymentProcessor()); + } + }, OperationContext.Stage.RUNTIME); + } + + @Override + protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List> newControllers) throws OperationFailedException { + super.performRuntime(context, operation, model, verificationHandler, newControllers); + + ServiceController controller = context.getServiceTarget() + .addService(KeycloakAdapterConfigService.SERVICE_NAME, KeycloakAdapterConfigService.INSTANCE) + .addListener(verificationHandler) + .setInitialMode(ServiceController.Mode.ACTIVE) + .install(); + newControllers.add(controller); + } + + @Override + protected boolean requiresRuntimeVerification() { + return false; + } +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemDefinition.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemDefinition.java new file mode 100644 index 0000000000..fe9b57b37a --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemDefinition.java @@ -0,0 +1,47 @@ +/* + * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.ReloadRequiredRemoveStepHandler; +import org.jboss.as.controller.SimpleResourceDefinition; +import org.jboss.as.controller.descriptions.ModelDescriptionConstants; +import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler; +import org.jboss.as.controller.registry.ManagementResourceRegistration; + +/** + * Definition of subsystem=keycloak. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class KeycloakSubsystemDefinition extends SimpleResourceDefinition { + protected KeycloakSubsystemDefinition() { + super(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME), + KeycloakExtension.getResourceDescriptionResolver("subsystem"), + KeycloakSubsystemAdd.INSTANCE, + ReloadRequiredRemoveStepHandler.INSTANCE + ); + } + + @Override + public void registerOperations(ManagementResourceRegistration resourceRegistration) { + super.registerOperations(resourceRegistration); + resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE); + } + +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemParser.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemParser.java new file mode 100644 index 0000000000..d4bd89bfe6 --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakSubsystemParser.java @@ -0,0 +1,227 @@ +/* + * Copyright 2014 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.SimpleAttributeDefinition; +import org.jboss.as.controller.descriptions.ModelDescriptionConstants; +import org.jboss.as.controller.operations.common.Util; +import org.jboss.as.controller.parsing.ParseUtils; +import org.jboss.as.controller.persistence.SubsystemMarshallingContext; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.Property; +import org.jboss.staxmapper.XMLElementReader; +import org.jboss.staxmapper.XMLElementWriter; +import org.jboss.staxmapper.XMLExtendedStreamReader; +import org.jboss.staxmapper.XMLExtendedStreamWriter; + +/** + * The subsystem parser, which uses stax to read and write to and from xml + */ +class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader>, XMLElementWriter { + + /** + * {@inheritDoc} + */ + @Override + public void readElement(final XMLExtendedStreamReader reader, final List list) throws XMLStreamException { + // Require no attributes + ParseUtils.requireNoAttributes(reader); + ModelNode addKeycloakSub = Util.createAddOperation(PathAddress.pathAddress(KeycloakExtension.PATH_SUBSYSTEM)); + list.add(addKeycloakSub); + + while (reader.hasNext() && nextTag(reader) != END_ELEMENT) { + if (!reader.getLocalName().equals("realm")) { + throw ParseUtils.unexpectedElement(reader); + } + readRealm(reader, list); + } + } + + // used for debugging + private int nextTag(XMLExtendedStreamReader reader) throws XMLStreamException { + return reader.nextTag(); + } + + private void readRealm(XMLExtendedStreamReader reader, List list) throws XMLStreamException { + String realmName = readNameAttribute(reader); + ModelNode composite = new ModelNode(); + composite.get(ModelDescriptionConstants.OP_ADDR).setEmptyList(); + composite.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.COMPOSITE); + ModelNode addRealm = new ModelNode(); + addRealm.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD); + PathAddress addr = PathAddress.pathAddress(PathElement.pathElement(ModelDescriptionConstants.SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME), + PathElement.pathElement(RealmDefinition.TAG_NAME, realmName)); + addRealm.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode()); + + List resourcesToAdd = new ArrayList(); + while (reader.hasNext() && nextTag(reader) != END_ELEMENT) { + String tagName = reader.getLocalName(); + if (tagName.equals(SecureDeploymentDefinition.TAG_NAME)) { + readDeployment(reader, addr, resourcesToAdd); + continue; + } + + SimpleAttributeDefinition def = RealmDefinition.lookup(tagName); + if (def == null) throw new XMLStreamException("Unknown realm tag " + tagName); + def.parseAndSetParameter(reader.getElementText(), addRealm, reader); + } + + if (!RealmDefinition.validateTruststoreSetIfRequired(addRealm)) { + //TODO: externalize the message + throw new XMLStreamException("truststore and truststore-password must be set if both ssl-not-required and disable-trust-maanger are false."); + } + + ModelNode steps = new ModelNode(); + steps.add(addRealm); + for (ModelNode resource : resourcesToAdd) { + steps.add(resource); + } + composite.get(ModelDescriptionConstants.STEPS).set(steps); + + list.add(composite); + } + + private void readDeployment(XMLExtendedStreamReader reader, PathAddress parent, List resourcesToAdd) throws XMLStreamException { + String name = readNameAttribute(reader); + ModelNode addSecureDeployment = new ModelNode(); + addSecureDeployment.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD); + PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(SecureDeploymentDefinition.TAG_NAME, name)); + addSecureDeployment.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode()); + List credentialsToAdd = new ArrayList(); + while (reader.hasNext() && nextTag(reader) != END_ELEMENT) { + String tagName = reader.getLocalName(); + if (tagName.equals(CredentialDefinition.TAG_NAME)) { + readCredential(reader, addr, credentialsToAdd); + continue; + } + + SimpleAttributeDefinition def = SecureDeploymentDefinition.lookup(tagName); + if (def == null) throw new XMLStreamException("Unknown secure-deployment tag " + tagName); + def.parseAndSetParameter(reader.getElementText(), addSecureDeployment, reader); + } + // Must add credentials after the deployment is added. + resourcesToAdd.add(addSecureDeployment); + resourcesToAdd.addAll(credentialsToAdd); + } + + public void readCredential(XMLExtendedStreamReader reader, PathAddress parent, List credentialsToAdd) throws XMLStreamException { + String name = readNameAttribute(reader); + ModelNode addCredential = new ModelNode(); + addCredential.get(ModelDescriptionConstants.OP).set(ModelDescriptionConstants.ADD); + PathAddress addr = PathAddress.pathAddress(parent, PathElement.pathElement(CredentialDefinition.TAG_NAME, name)); + addCredential.get(ModelDescriptionConstants.OP_ADDR).set(addr.toModelNode()); + addCredential.get(CredentialDefinition.VALUE.getName()).set(reader.getElementText()); + credentialsToAdd.add(addCredential); + } + + // expects that the current tag will have one single attribute called "name" + private String readNameAttribute(XMLExtendedStreamReader reader) throws XMLStreamException { + String name = null; + for (int i = 0; i < reader.getAttributeCount(); i++) { + String attr = reader.getAttributeLocalName(i); + if (attr.equals("name")) { + name = reader.getAttributeValue(i); + continue; + } + throw ParseUtils.unexpectedAttribute(reader, i); + } + if (name == null) { + throw ParseUtils.missingRequired(reader, Collections.singleton("name")); + } + return name; + } + + /** + * {@inheritDoc} + */ + @Override + public void writeContent(final XMLExtendedStreamWriter writer, final SubsystemMarshallingContext context) throws XMLStreamException { + context.startSubsystemElement(KeycloakExtension.NAMESPACE, false); + writeRealms(writer, context); + writer.writeEndElement(); + } + + private void writeRealms(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException { + if (!context.getModelNode().get(RealmDefinition.TAG_NAME).isDefined()) { + return; + } + for (Property realm : context.getModelNode().get(RealmDefinition.TAG_NAME).asPropertyList()) { + writer.writeStartElement(RealmDefinition.TAG_NAME); + writer.writeAttribute("name", realm.getName()); + ModelNode realmElements = realm.getValue(); + for (AttributeDefinition element : RealmDefinition.ALL_ATTRIBUTES) { + element.marshallAsElement(realmElements, writer); + } + + ModelNode deployments = realmElements.get(SecureDeploymentDefinition.TAG_NAME); + if (deployments.isDefined()) { + writeSecureDeployments(writer, deployments); + } + + writer.writeEndElement(); + } + } + + private void writeSecureDeployments(XMLExtendedStreamWriter writer, ModelNode deployments) throws XMLStreamException { + for (Property deployment : deployments.asPropertyList()) { + writer.writeStartElement(SecureDeploymentDefinition.TAG_NAME); + writer.writeAttribute("name", deployment.getName()); + ModelNode deploymentElements = deployment.getValue(); + for (AttributeDefinition element : SecureDeploymentDefinition.ALL_ATTRIBUTES) { + element.marshallAsElement(deploymentElements, writer); + } + + ModelNode credentials = deploymentElements.get(CredentialDefinition.TAG_NAME); + if (credentials.isDefined()) { + writeCredentials(writer, credentials); + } + + writer.writeEndElement(); + } + } + + private void writeCredentials(XMLExtendedStreamWriter writer, ModelNode credentials) throws XMLStreamException { + for (Property credential : credentials.asPropertyList()) { + writer.writeStartElement(CredentialDefinition.TAG_NAME); + writer.writeAttribute("name", credential.getName()); + String credentialValue = credential.getValue().get(CredentialDefinition.VALUE.getName()).asString(); + writeCharacters(writer, credentialValue); + writer.writeEndElement(); + } + } + + // code taken from org.jboss.as.controller.AttributeMarshaller + private void writeCharacters(XMLExtendedStreamWriter writer, String content) throws XMLStreamException { + if (content.indexOf('\n') > -1) { + // Multiline content. Use the overloaded variant that staxmapper will format + writer.writeCharacters(content); + } else { + // Staxmapper will just output the chars without adding newlines if this is used + char[] chars = content.toCharArray(); + writer.writeCharacters(chars, 0, chars.length); + } + } + +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/RealmAddHandler.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/RealmAddHandler.java new file mode 100644 index 0000000000..9705980968 --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/RealmAddHandler.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import java.util.List; +import org.jboss.as.controller.AbstractAddStepHandler; +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.ServiceVerificationHandler; +import org.jboss.dmr.ModelNode; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; +import org.jboss.msc.service.ServiceController; + +/** + * Add a new realm. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class RealmAddHandler extends AbstractAddStepHandler { + + public static RealmAddHandler INSTANCE = new RealmAddHandler(); + + private RealmAddHandler() {} + + @Override + protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException { + // TODO: localize exception. get id number + if (!operation.get(OP).asString().equals(ADD)) { + throw new OperationFailedException("Unexpected operation for add realm. operation=" + operation.toString()); + } + + for (AttributeDefinition attrib : RealmDefinition.ALL_ATTRIBUTES) { + attrib.validateAndSet(operation, model); + } + + if (!RealmDefinition.validateTruststoreSetIfRequired(model.clone())) { + //TODO: externalize message + throw new OperationFailedException("truststore and truststore-password must be set if both ssl-not-required and disable-trust-maanger are false."); + } + } + + @Override + protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List> newControllers) throws OperationFailedException { + KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.find(context); + ckService.addRealm(operation, model); + } +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/RealmDefinition.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/RealmDefinition.java new file mode 100644 index 0000000000..7ecdcb3fc5 --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/RealmDefinition.java @@ -0,0 +1,173 @@ +/* + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.SimpleAttributeDefinition; +import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; +import org.jboss.as.controller.SimpleResourceDefinition; +import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler; +import org.jboss.as.controller.operations.validation.IntRangeValidator; +import org.jboss.as.controller.operations.validation.StringLengthValidator; +import org.jboss.as.controller.registry.ManagementResourceRegistration; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.ModelType; + +/** + * Defines attributes and operations for the Realm + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class RealmDefinition extends SimpleResourceDefinition { + + public static final String TAG_NAME = "realm"; + + protected static final SimpleAttributeDefinition REALM_PUBLIC_KEY = + new SimpleAttributeDefinitionBuilder("realm-public-key", ModelType.STRING, false) + .setXmlName("realm-public-key") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, false, true)) + .build(); + protected static final SimpleAttributeDefinition AUTH_SERVER_URL = + new SimpleAttributeDefinitionBuilder("auth-server-url", ModelType.STRING, false) + .setXmlName("auth-server-url") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, false, true)) + .build(); + protected static final SimpleAttributeDefinition SSL_NOT_REQUIRED = + new SimpleAttributeDefinitionBuilder("ssl-not-required", ModelType.BOOLEAN, true) + .setXmlName("ssl-not-required") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(false)) + .build(); + protected static final SimpleAttributeDefinition ALLOW_ANY_HOSTNAME = + new SimpleAttributeDefinitionBuilder("allow-any-hostname", ModelType.BOOLEAN, true) + .setXmlName("allow-any-hostname") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(false)) + .build(); + protected static final SimpleAttributeDefinition DISABLE_TRUST_MANAGER = + new SimpleAttributeDefinitionBuilder("disable-trust-manager", ModelType.BOOLEAN, true) + .setXmlName("disable-trust-manager") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(false)) + .build(); + protected static final SimpleAttributeDefinition TRUSTSTORE = + new SimpleAttributeDefinitionBuilder("truststore", ModelType.STRING, true) + .setXmlName("truststore") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition TRUSTSTORE_PASSWORD = + new SimpleAttributeDefinitionBuilder("truststore-password", ModelType.STRING, true) + .setXmlName("truststore-password") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition CONNECTION_POOL_SIZE = + new SimpleAttributeDefinitionBuilder("connection-pool-size", ModelType.INT, true) + .setXmlName("connection-pool-size") + .setAllowExpression(true) + .setValidator(new IntRangeValidator(0)) + .build(); + + protected static final List REALM_ONLY_ATTRIBUTES = new ArrayList(); + static { + REALM_ONLY_ATTRIBUTES.add(REALM_PUBLIC_KEY); + REALM_ONLY_ATTRIBUTES.add(AUTH_SERVER_URL); + REALM_ONLY_ATTRIBUTES.add(TRUSTSTORE); + REALM_ONLY_ATTRIBUTES.add(TRUSTSTORE_PASSWORD); + REALM_ONLY_ATTRIBUTES.add(SSL_NOT_REQUIRED); + REALM_ONLY_ATTRIBUTES.add(ALLOW_ANY_HOSTNAME); + REALM_ONLY_ATTRIBUTES.add(DISABLE_TRUST_MANAGER); + REALM_ONLY_ATTRIBUTES.add(CONNECTION_POOL_SIZE); + } + + protected static final List ALL_ATTRIBUTES = new ArrayList(); + static { + ALL_ATTRIBUTES.addAll(REALM_ONLY_ATTRIBUTES); + ALL_ATTRIBUTES.addAll(SharedAttributeDefinitons.ATTRIBUTES); + } + + private static final Map DEFINITION_LOOKUP = new HashMap(); + static { + for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) { + DEFINITION_LOOKUP.put(def.getXmlName(), def); + } + } + + private static final RealmWriteAttributeHandler realmAttrHandler = new RealmWriteAttributeHandler(ALL_ATTRIBUTES.toArray(new SimpleAttributeDefinition[0])); + + public RealmDefinition() { + super(PathElement.pathElement("realm"), + KeycloakExtension.getResourceDescriptionResolver("realm"), + RealmAddHandler.INSTANCE, + RealmRemoveHandler.INSTANCE); + } + + @Override + public void registerOperations(ManagementResourceRegistration resourceRegistration) { + super.registerOperations(resourceRegistration); + resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE); + } + + @Override + public void registerAttributes(ManagementResourceRegistration resourceRegistration) { + super.registerAttributes(resourceRegistration); + + for (AttributeDefinition attrDef : ALL_ATTRIBUTES) { + //TODO: use subclass of realmAttrHandler that can call RealmDefinition.validateTruststoreSetIfRequired + resourceRegistration.registerReadWriteAttribute(attrDef, null, realmAttrHandler); + } + } + + /** + * truststore and truststore-password must be set if ssl-not-required and disable-trust-manager are both false. + * + * @param attributes The full set of attributes. + * + * @return true if the attributes are valid, false otherwise. + */ + public static boolean validateTruststoreSetIfRequired(ModelNode attributes) { + if (!isSet(attributes, SSL_NOT_REQUIRED) && !isSet(attributes, DISABLE_TRUST_MANAGER)) { + if (!(isSet(attributes, TRUSTSTORE) && isSet(attributes, TRUSTSTORE_PASSWORD))) { + return false; + } + } + + return true; + } + + private static boolean isSet(ModelNode attributes, SimpleAttributeDefinition def) { + ModelNode attribute = attributes.get(def.getName()); + + if (def.getType() == ModelType.BOOLEAN) { + return attribute.isDefined() && attribute.asBoolean(); + } + + return attribute.isDefined() && !attribute.asString().isEmpty(); + } + + public static SimpleAttributeDefinition lookup(String name) { + return DEFINITION_LOOKUP.get(name); + } +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/RealmRemoveHandler.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/RealmRemoveHandler.java new file mode 100644 index 0000000000..98274875dd --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/RealmRemoveHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import org.jboss.as.controller.AbstractRemoveStepHandler; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.dmr.ModelNode; + +/** + * Remove a realm. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class RealmRemoveHandler extends AbstractRemoveStepHandler { + + public static RealmRemoveHandler INSTANCE = new RealmRemoveHandler(); + + private RealmRemoveHandler() {} + + @Override + protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException { + KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.find(context); + ckService.removeRealm(operation); + } +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/RealmWriteAttributeHandler.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/RealmWriteAttributeHandler.java new file mode 100644 index 0000000000..f6847b5d5d --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/RealmWriteAttributeHandler.java @@ -0,0 +1,59 @@ +/* + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import java.util.List; +import org.jboss.as.controller.AbstractWriteAttributeHandler; +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.dmr.ModelNode; + +/** + * Update an attribute on a realm. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class RealmWriteAttributeHandler extends AbstractWriteAttributeHandler { + + public RealmWriteAttributeHandler(List definitions) { + this(definitions.toArray(new AttributeDefinition[definitions.size()])); + } + + public RealmWriteAttributeHandler(AttributeDefinition... definitions) { + super(definitions); + } + + @Override + protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, + ModelNode resolvedValue, ModelNode currentValue, HandbackHolder hh) throws OperationFailedException { + KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.find(context); + ckService.updateRealm(operation, attributeName, resolvedValue); + + hh.setHandback(ckService); + + return false; + } + + @Override + protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, + ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException { + ckService.updateRealm(operation, attributeName, valueToRestore); + } + +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentAddHandler.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentAddHandler.java new file mode 100644 index 0000000000..20930b0abf --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentAddHandler.java @@ -0,0 +1,60 @@ +/* + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import java.util.List; +import org.jboss.as.controller.AbstractAddStepHandler; +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.ServiceVerificationHandler; +import org.jboss.dmr.ModelNode; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; +import org.jboss.msc.service.ServiceController; + +/** + * Add a deployment to a realm. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class SecureDeploymentAddHandler extends AbstractAddStepHandler { + + public static SecureDeploymentAddHandler INSTANCE = new SecureDeploymentAddHandler(); + + private SecureDeploymentAddHandler() {} + + @Override + protected void populateModel(ModelNode operation, ModelNode model) throws OperationFailedException { + // TODO: localize exception. get id number + if (!operation.get(OP).asString().equals(ADD)) { + throw new OperationFailedException("Unexpected operation for add secure deployment. operation=" + operation.toString()); + } + + for (AttributeDefinition attr : SecureDeploymentDefinition.ALL_ATTRIBUTES) { + attr.validateAndSet(operation, model); + } + } + + @Override + protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model, ServiceVerificationHandler verificationHandler, List> newControllers) throws OperationFailedException { + KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.find(context); + ckService.addSecureDeployment(operation, model); + } +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentDefinition.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentDefinition.java new file mode 100644 index 0000000000..9754c86db9 --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentDefinition.java @@ -0,0 +1,108 @@ +/* + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.PathElement; +import org.jboss.as.controller.SimpleAttributeDefinition; +import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; +import org.jboss.as.controller.SimpleResourceDefinition; +import org.jboss.as.controller.operations.common.GenericSubsystemDescribeHandler; +import org.jboss.as.controller.operations.validation.StringLengthValidator; +import org.jboss.as.controller.registry.ManagementResourceRegistration; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.ModelType; + +/** + * Defines attributes and operations for a secure-deployment. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class SecureDeploymentDefinition extends SimpleResourceDefinition { + + public static final String TAG_NAME = "secure-deployment"; + + protected static final SimpleAttributeDefinition RESOURCE = + new SimpleAttributeDefinitionBuilder("resource", ModelType.STRING, true) + .setXmlName("resource") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition USE_RESOURCE_ROLE_MAPPINGS = + new SimpleAttributeDefinitionBuilder("use-resource-role-mappings", ModelType.BOOLEAN, true) + .setXmlName("use-resource-role-mappings") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(false)) + .build(); + protected static final SimpleAttributeDefinition BEARER_ONLY = + new SimpleAttributeDefinitionBuilder("bearer-only", ModelType.BOOLEAN, true) + .setXmlName("bearer-only") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(false)) + .build(); + + protected static final List DEPLOYMENT_ONLY_ATTRIBUTES = new ArrayList(); + static { + DEPLOYMENT_ONLY_ATTRIBUTES.add(RESOURCE); + DEPLOYMENT_ONLY_ATTRIBUTES.add(USE_RESOURCE_ROLE_MAPPINGS); + DEPLOYMENT_ONLY_ATTRIBUTES.add(BEARER_ONLY); + } + + protected static final List ALL_ATTRIBUTES = new ArrayList(); + static { + ALL_ATTRIBUTES.addAll(DEPLOYMENT_ONLY_ATTRIBUTES); + ALL_ATTRIBUTES.addAll(SharedAttributeDefinitons.ATTRIBUTES); + } + + private static final Map DEFINITION_LOOKUP = new HashMap(); + static { + for (SimpleAttributeDefinition def : ALL_ATTRIBUTES) { + DEFINITION_LOOKUP.put(def.getXmlName(), def); + } + } + + private static SecureDeploymentWriteAttributeHandler attrHandler = new SecureDeploymentWriteAttributeHandler(ALL_ATTRIBUTES); + + public SecureDeploymentDefinition() { + super(PathElement.pathElement(TAG_NAME), + KeycloakExtension.getResourceDescriptionResolver(TAG_NAME), + SecureDeploymentAddHandler.INSTANCE, + SecureDeploymentRemoveHandler.INSTANCE); + } + + @Override + public void registerOperations(ManagementResourceRegistration resourceRegistration) { + super.registerOperations(resourceRegistration); + resourceRegistration.registerOperationHandler(GenericSubsystemDescribeHandler.DEFINITION, GenericSubsystemDescribeHandler.INSTANCE); + } + + @Override + public void registerAttributes(ManagementResourceRegistration resourceRegistration) { + super.registerAttributes(resourceRegistration); + for (AttributeDefinition attrDef : ALL_ATTRIBUTES) { + resourceRegistration.registerReadWriteAttribute(attrDef, null, attrHandler); + } + } + + public static SimpleAttributeDefinition lookup(String name) { + return DEFINITION_LOOKUP.get(name); + } +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentRemoveHandler.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentRemoveHandler.java new file mode 100644 index 0000000000..fedd88f5f4 --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentRemoveHandler.java @@ -0,0 +1,41 @@ +/* + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import org.jboss.as.controller.AbstractRemoveStepHandler; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.dmr.ModelNode; + +/** + * Remove a secure-deployment from a realm. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class SecureDeploymentRemoveHandler extends AbstractRemoveStepHandler { + + public static SecureDeploymentRemoveHandler INSTANCE = new SecureDeploymentRemoveHandler(); + + private SecureDeploymentRemoveHandler() {} + + @Override + protected void performRuntime(OperationContext context, ModelNode operation, ModelNode model) throws OperationFailedException { + KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.find(context); + ckService.removeSecureDeployment(operation); + } +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentWriteAttributeHandler.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentWriteAttributeHandler.java new file mode 100644 index 0000000000..8cc1f604d8 --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/SecureDeploymentWriteAttributeHandler.java @@ -0,0 +1,58 @@ +/* + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import java.util.List; +import org.jboss.as.controller.AbstractWriteAttributeHandler; +import org.jboss.as.controller.AttributeDefinition; +import org.jboss.as.controller.OperationContext; +import org.jboss.as.controller.OperationFailedException; +import org.jboss.as.controller.SimpleAttributeDefinition; +import org.jboss.dmr.ModelNode; + +/** + * Update an attribute on a secure-deployment. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class SecureDeploymentWriteAttributeHandler extends AbstractWriteAttributeHandler { + + public SecureDeploymentWriteAttributeHandler(List definitions) { + this(definitions.toArray(new AttributeDefinition[definitions.size()])); + } + + public SecureDeploymentWriteAttributeHandler(AttributeDefinition... definitions) { + super(definitions); + } + + @Override + protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, + ModelNode resolvedValue, ModelNode currentValue, HandbackHolder hh) throws OperationFailedException { + KeycloakAdapterConfigService ckService = KeycloakAdapterConfigService.find(context); + hh.setHandback(ckService); + ckService.updateSecureDeployment(operation, attributeName, resolvedValue); + return false; + } + + @Override + protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, + ModelNode valueToRestore, ModelNode valueToRevert, KeycloakAdapterConfigService ckService) throws OperationFailedException { + ckService.updateSecureDeployment(operation, attributeName, valueToRestore); + } + +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/SharedAttributeDefinitons.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/SharedAttributeDefinitons.java new file mode 100644 index 0000000000..db0ccf8986 --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/SharedAttributeDefinitons.java @@ -0,0 +1,97 @@ +/* + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + +import java.util.ArrayList; +import java.util.List; +import org.jboss.as.controller.SimpleAttributeDefinition; +import org.jboss.as.controller.SimpleAttributeDefinitionBuilder; +import org.jboss.as.controller.operations.validation.IntRangeValidator; +import org.jboss.as.controller.operations.validation.StringLengthValidator; +import org.jboss.dmr.ModelNode; +import org.jboss.dmr.ModelType; + +/** + * Defines attributes that can be present in both a realm and an application (secure-deployment). + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class SharedAttributeDefinitons { + + protected static final SimpleAttributeDefinition ENABLE_CORS = + new SimpleAttributeDefinitionBuilder("enable-cors", ModelType.BOOLEAN, true) + .setXmlName("enable-cors") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(false)) + .build(); + protected static final SimpleAttributeDefinition CLIENT_KEYSTORE = + new SimpleAttributeDefinitionBuilder("client-keystore", ModelType.STRING, true) + .setXmlName("client-keystore") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition CLIENT_KEYSTORE_PASSWORD = + new SimpleAttributeDefinitionBuilder("client-keystore-password", ModelType.STRING, true) + .setXmlName("client-keystore-password") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition CLIENT_KEY_PASSWORD = + new SimpleAttributeDefinitionBuilder("client-key-password", ModelType.STRING, true) + .setXmlName("client-key-password") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition CORS_MAX_AGE = + new SimpleAttributeDefinitionBuilder("cors-max-age", ModelType.INT, true) + .setXmlName("cors-max-age") + .setAllowExpression(true) + .setValidator(new IntRangeValidator(-1)) + .build(); + protected static final SimpleAttributeDefinition CORS_ALLOWED_HEADERS = + new SimpleAttributeDefinitionBuilder("cors-allowed-headers", ModelType.STRING, true) + .setXmlName("cors-allowed-headers") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition CORS_ALLOWED_METHODS = + new SimpleAttributeDefinitionBuilder("cors-allowed-methods", ModelType.STRING, true) + .setXmlName("cors-allowed-methods") + .setAllowExpression(true) + .setValidator(new StringLengthValidator(1, Integer.MAX_VALUE, true, true)) + .build(); + protected static final SimpleAttributeDefinition EXPOSE_TOKEN = + new SimpleAttributeDefinitionBuilder("expose-token", ModelType.BOOLEAN, true) + .setXmlName("expose-token") + .setAllowExpression(true) + .setDefaultValue(new ModelNode(false)) + .build(); + + + protected static final List ATTRIBUTES = new ArrayList(); + static { + ATTRIBUTES.add(ENABLE_CORS); + ATTRIBUTES.add(CLIENT_KEYSTORE); + ATTRIBUTES.add(CLIENT_KEYSTORE_PASSWORD); + ATTRIBUTES.add(CLIENT_KEY_PASSWORD); + ATTRIBUTES.add(CORS_MAX_AGE); + ATTRIBUTES.add(CORS_ALLOWED_HEADERS); + ATTRIBUTES.add(CORS_ALLOWED_METHODS); + ATTRIBUTES.add(EXPOSE_TOKEN); + } + +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakLogger.java b/subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakLogger.java new file mode 100644 index 0000000000..bca4588b42 --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakLogger.java @@ -0,0 +1,67 @@ +/* + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.logging; + +import java.util.List; +import org.jboss.logging.BasicLogger; +import org.jboss.logging.annotations.LogMessage; +import org.jboss.logging.Logger; +import org.jboss.logging.annotations.Message; +import org.jboss.logging.annotations.MessageLogger; +import org.jboss.vfs.VirtualFile; + +import static org.jboss.logging.Logger.Level.ERROR; +import static org.jboss.logging.Logger.Level.INFO; +import static org.jboss.logging.Logger.Level.WARN; + +/** + * This interface to be fleshed out later when error messages are fully externalized. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +@MessageLogger(projectCode = "KEYCLOAK") +public interface KeycloakLogger extends BasicLogger { + + /** + * A logger with a category of the package name. + */ + KeycloakLogger ROOT_LOGGER = Logger.getMessageLogger(KeycloakLogger.class, "org.jboss.keycloak"); + + /* @LogMessage(level = ERROR) + @Message(id = 12600, value = "Could not load JSF managed bean class: %s") + void managedBeanLoadFail(String managedBean); + + @LogMessage(level = ERROR) + @Message(id = 12601, value = "JSF managed bean class %s has no default constructor") + void managedBeanNoDefaultConstructor(String managedBean); + + @LogMessage(level = ERROR) + @Message(id = 12602, value = "Failed to parse %s, managed beans defined in this file will not be available") + void managedBeansConfigParseFailed(VirtualFile facesConfig); + + @LogMessage(level = WARN) + @Message(id = 12603, value = "Unknown JSF version '%s'. Default version '%s' will be used instead.") + void unknownJSFVersion(String version, String defaultVersion); + + @LogMessage(level = WARN) + @Message(id = 12604, value = "JSF version slot '%s' is missing from module %s") + void missingJSFModule(String version, String module); + + @LogMessage(level = INFO) + @Message(id = 12605, value = "Activated the following JSF Implementations: %s") + void activatedJSFImplementations(List target); */ +} diff --git a/subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakMessages.java b/subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakMessages.java new file mode 100644 index 0000000000..529049dbfa --- /dev/null +++ b/subsystem/src/main/java/org/keycloak/subsystem/logging/KeycloakMessages.java @@ -0,0 +1,61 @@ +/* + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.logging; + +import org.jboss.as.server.deployment.DeploymentUnitProcessingException; +import org.jboss.jandex.AnnotationTarget; +import org.jboss.jandex.DotName; +import org.jboss.logging.annotations.Cause; +import org.jboss.logging.annotations.Message; +import org.jboss.logging.annotations.MessageBundle; +import org.jboss.logging.Messages; + +/** + * This interface to be fleshed out later when error messages are fully externalized. + * + * @author Stan Silvert ssilvert@redhat.com (C) 2012 Red Hat Inc. + */ +@MessageBundle(projectCode = "TLIP") +public interface KeycloakMessages { + + /** + * The messages + */ + KeycloakMessages MESSAGES = Messages.getBundle(KeycloakMessages.class); +/* + @Message(id = 12650, value = "Failed to load annotated class: %s") + String classLoadingFailed(DotName clazz); + + @Message(id = 12651, value = "Annotation %s in class %s is only allowed on classes") + String invalidAnnotationLocation(Object annotation, AnnotationTarget classInfo); + + @Message(id = 12652, value = "Instance creation failed") + RuntimeException instanceCreationFailed(@Cause Throwable t); + + @Message(id = 12653, value = "Instance destruction failed") + RuntimeException instanceDestructionFailed(@Cause Throwable t); + + @Message(id = 12654, value = "Thread local injection container not set") + IllegalStateException noThreadLocalInjectionContainer(); + + @Message(id = 12655, value = "@ManagedBean is only allowed at class level %s") + String invalidManagedBeanAnnotation(AnnotationTarget target); + + @Message(id = 12656, value = "Default JSF implementation slot '%s' is invalid") + DeploymentUnitProcessingException invalidDefaultJSFImpl(String defaultJsfVersion); + */ +} diff --git a/subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension b/subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension new file mode 100644 index 0000000000..6a7d631da0 --- /dev/null +++ b/subsystem/src/main/resources/META-INF/services/org.jboss.as.controller.Extension @@ -0,0 +1 @@ +org.keycloak.subsystem.extension.KeycloakExtension diff --git a/subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties b/subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties new file mode 100644 index 0000000000..5234a327e3 --- /dev/null +++ b/subsystem/src/main/resources/org/keycloak/subsystem/extension/LocalDescriptions.properties @@ -0,0 +1,49 @@ +keycloak.subsystem=Keycloak subsystem +keycloak.subsystem.add=Operation Adds Keycloak subsystem +keycloak.subsystem.remove=Operation removes Keycloak subsystem +keycloak.subsystem.realm=A Keycloak realm. + +keycloak.realm=A Keycloak realm. +keycloak.realm.add=Add a realm definition to the subsystem. +keycloak.realm.remove=Remove a realm from the subsystem. +keycloak.realm.realm-public-key=TODO: fill in help text +keycloak.realm.auth-server-url=TODO: fill in help text +keycloak.realm.disable-trust-manager=TODO: fill in help text +keycloak.realm.ssl-not-required=TODO: fill in help text +keycloak.realm.allow-any-hostname=TODO: fill in help text +keycloak.realm.truststore=TODO: fill in help text +keycloak.realm.truststore-password=TODO: fill in help text +keycloak.realm.connection-pool-size=TODO: fill in help text +keycloak.realm.enable-cors=TODO: fill in help text +keycloak.realm.client-keystore=TODO: fill in help text +keycloak.realm.client-keystore-password=TODO: fill in help text +keycloak.realm.client-key-password=TODO: fill in help text +keycloak.realm.cors-max-age=TODO: fill in help text +keycloak.realm.cors-allowed-headers=TODO: fill in help text +keycloak.realm.cors-allowed-methods=TODO: fill in help text +keycloak.realm.expose-token=TODO: fill in help text + +keycloak.realm.secure-deployment=A deployment secured by Keycloak + +keycloak.secure-deployment=A deployment secured by Keycloak +keycloak.secure-deployment.add=Add a deployment to be secured by Keycloak +keycloak.secure-deployment.remove=Remove a deployment to be secured by Keycloak +keycloak.secure-deployment.resource=TODO: fill in help text +keycloak.secure-deployment.use-resource-role-mappings=TODO: fill in help text +keycloak.secure-deployment.credentials=TODO: fill in help text +keycloak.secure-deployment.bearer-only=TODO: fill in help text +keycloak.secure-deployment.enable-cors=TODO: fill in help text +keycloak.secure-deployment.client-keystore=TODO: fill in help text +keycloak.secure-deployment.client-keystore-password=TODO: fill in help text +keycloak.secure-deployment.client-key-password=TODO: fill in help text +keycloak.secure-deployment.cors-max-age=TODO: fill in help text +keycloak.secure-deployment.cors-allowed-headers=TODO: fill in help text +keycloak.secure-deployment.cors-allowed-methods=TODO: fill in help text +keycloak.secure-deployment.expose-token=TODO: fill in help text + +keycloak.secure-deployment.credential=TODO: fill in help text + +keycloak.credential=TODO: fill in help text +keycloak.credential.value=TODO: fill in help text +keycloak.credential.add=TODO: fill in help text +keycloak.credential.remove=TODO: fill in help text \ No newline at end of file diff --git a/subsystem/src/main/resources/schema/keycloak_1_0.xsd b/subsystem/src/main/resources/schema/keycloak_1_0.xsd new file mode 100644 index 0000000000..9cdbbba980 --- /dev/null +++ b/subsystem/src/main/resources/schema/keycloak_1_0.xsd @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + The name of the realm. + + + + + + + + + + + + + + + + + + + + + + + + The name of the deployment. + + + + + + + + + + + + + + + + The name of the credential. + + + + + + + + + + + + + + + + + + + + + diff --git a/subsystem/src/test/java/org/keycloak/subsystem/extension/RealmDefinitionTestCase.java b/subsystem/src/test/java/org/keycloak/subsystem/extension/RealmDefinitionTestCase.java new file mode 100644 index 0000000000..5cff75e2c5 --- /dev/null +++ b/subsystem/src/test/java/org/keycloak/subsystem/extension/RealmDefinitionTestCase.java @@ -0,0 +1,79 @@ +/* + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + + +import org.jboss.dmr.ModelNode; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + + + +/** + * + * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. + */ +public class RealmDefinitionTestCase { + + private ModelNode model; + + @Before + public void setUp() { + model = new ModelNode(); + model.get("realm").set("demo"); + model.get("resource").set("customer-portal"); + model.get("realm-public-key").set("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"); + model.get("auth-url").set("http://localhost:8080/auth-server/rest/realms/demo/tokens/login"); + model.get("code-url").set("http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes"); + model.get("expose-token").set(true); + ModelNode credential = new ModelNode(); + credential.get("password").set("password"); + model.get("credentials").set(credential); + } + + @Test + public void testIsTruststoreSetIfRequired() throws Exception { + model.get("ssl-not-required").set(true); + model.get("disable-trust-manager").set(true); + Assert.assertTrue(RealmDefinition.validateTruststoreSetIfRequired(model)); + + model.get("ssl-not-required").set(true); + model.get("disable-trust-manager").set(false); + Assert.assertTrue(RealmDefinition.validateTruststoreSetIfRequired(model)); + + model.get("ssl-not-required").set(false); + model.get("disable-trust-manager").set(true); + Assert.assertTrue(RealmDefinition.validateTruststoreSetIfRequired(model)); + + model.get("ssl-not-required").set(false); + model.get("disable-trust-manager").set(false); + Assert.assertFalse(RealmDefinition.validateTruststoreSetIfRequired(model)); + + model.get("ssl-not-required").set(false); + model.get("disable-trust-manager").set(false); + model.get("truststore").set("foo"); + Assert.assertFalse(RealmDefinition.validateTruststoreSetIfRequired(model)); + + model.get("ssl-not-required").set(false); + model.get("disable-trust-manager").set(false); + model.get("truststore").set("foo"); + model.get("truststore-password").set("password"); + Assert.assertTrue(RealmDefinition.validateTruststoreSetIfRequired(model)); + } + +} diff --git a/subsystem/src/test/java/org/keycloak/subsystem/extension/SubsystemParsingTestCase.java b/subsystem/src/test/java/org/keycloak/subsystem/extension/SubsystemParsingTestCase.java new file mode 100644 index 0000000000..090feb12e8 --- /dev/null +++ b/subsystem/src/test/java/org/keycloak/subsystem/extension/SubsystemParsingTestCase.java @@ -0,0 +1,175 @@ +/* + * Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors + * as indicated by the @author tags. All rights reserved. + * + * 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 org.keycloak.subsystem.extension; + + +import junit.framework.Assert; +import org.jboss.as.controller.PathAddress; +import org.jboss.as.controller.PathElement; +import org.jboss.as.subsystem.test.AbstractSubsystemTest; +import org.jboss.as.subsystem.test.KernelServices; +import org.jboss.dmr.ModelNode; +import org.junit.Test; + +import java.util.List; + +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DESCRIBE; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR; +import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUBSYSTEM; + + +/** + * Tests all management expects for subsystem, parsing, marshaling, model definition and other + * Here is an example that allows you a fine grained controller over what is tested and how. So it can give you ideas what can be done and tested. + * If you have no need for advanced testing of subsystem you look at {@link SubsystemBaseParsingTestCase} that testes same stuff but most of the code + * is hidden inside of test harness + * + * @author Kabir Khan + */ +public class SubsystemParsingTestCase extends AbstractSubsystemTest { + + public SubsystemParsingTestCase() { + super(KeycloakExtension.SUBSYSTEM_NAME, new KeycloakExtension()); + } + + @Test + public void testJson() throws Exception { + ModelNode node = new ModelNode(); + node.get("realm").set("demo"); + node.get("resource").set("customer-portal"); + node.get("realm-public-key").set("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"); + node.get("auth-url").set("http://localhost:8080/auth-server/rest/realms/demo/tokens/login"); + node.get("code-url").set("http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes"); + node.get("ssl-not-required").set(true); + node.get("expose-token").set(true); + ModelNode credential = new ModelNode(); + credential.get("password").set("password"); + node.get("credentials").set(credential); + + System.out.println("json=" + node.toJSONString(false)); + } + /** + * Tests that the xml is parsed into the correct operations + */ + @Test + public void testParseSubsystem() throws Exception { + //Parse the subsystem xml into operations + String subsystemXml = + "" + + ""; + List operations = super.parse(subsystemXml); + + ///Check that we have the expected number of operations + Assert.assertEquals(1, operations.size()); + + //Check that each operation has the correct content + ModelNode addSubsystem = operations.get(0); + Assert.assertEquals(ADD, addSubsystem.get(OP).asString()); + PathAddress addr = PathAddress.pathAddress(addSubsystem.get(OP_ADDR)); + Assert.assertEquals(1, addr.size()); + PathElement element = addr.getElement(0); + Assert.assertEquals(SUBSYSTEM, element.getKey()); + Assert.assertEquals(KeycloakExtension.SUBSYSTEM_NAME, element.getValue()); + } + + /** + * Test that the model created from the xml looks as expected + */ + @Test + public void testInstallIntoController() throws Exception { + //Parse the subsystem xml and install into the controller + String subsystemXml = + "" + + ""; + KernelServices services = super.installInController(subsystemXml); + + //Read the whole model and make sure it looks as expected + ModelNode model = services.readWholeModel(); + Assert.assertTrue(model.get(SUBSYSTEM).hasDefined(KeycloakExtension.SUBSYSTEM_NAME)); + } + + /** + * Starts a controller with a given subsystem xml and then checks that a second + * controller started with the xml marshalled from the first one results in the same model + */ + @Test + public void testParseAndMarshalModel() throws Exception { + //Parse the subsystem xml and install into the first controller + //TODO: Figure out why this fails + String subsystemXml = + "" + + ""; + KernelServices servicesA = super.installInController(subsystemXml); + //Get the model and the persisted xml from the first controller + ModelNode modelA = servicesA.readWholeModel(); + String marshalled = servicesA.getPersistedSubsystemXml(); + + //Install the persisted xml from the first controller into a second controller + KernelServices servicesB = super.installInController(marshalled); + ModelNode modelB = servicesB.readWholeModel(); + + //Make sure the models from the two controllers are identical + super.compare(modelA, modelB); + } + + /** + * Starts a controller with the given subsystem xml and then checks that a second + * controller started with the operations from its describe action results in the same model + */ + @Test + public void testDescribeHandler() throws Exception { + //Parse the subsystem xml and install into the first controller + String subsystemXml = + "" + + ""; + KernelServices servicesA = super.installInController(subsystemXml); + //Get the model and the describe operations from the first controller + ModelNode modelA = servicesA.readWholeModel(); + ModelNode describeOp = new ModelNode(); + describeOp.get(OP).set(DESCRIBE); + describeOp.get(OP_ADDR).set( + PathAddress.pathAddress( + PathElement.pathElement(SUBSYSTEM, KeycloakExtension.SUBSYSTEM_NAME)).toModelNode()); + List operations = super.checkResultAndGetContents(servicesA.executeOperation(describeOp)).asList(); + + + //Install the describe options from the first controller into a second controller + KernelServices servicesB = super.installInController(operations); + ModelNode modelB = servicesB.readWholeModel(); + + //Make sure the models from the two controllers are identical + super.compare(modelA, modelB); + } + + /** + * Tests that the subsystem can be removed + */ + @Test + public void testSubsystemRemoval() throws Exception { + //Parse the subsystem xml and install into the first controller + String subsystemXml = + "" + + ""; + KernelServices services = super.installInController(subsystemXml); + //Checks that the subsystem was removed from the model + super.assertRemoveSubsystemResources(services); + + //TODO Chek that any services that were installed were removed here + } +} From ca8deb6f5385d995242a9b783dbe9b4aec21653a Mon Sep 17 00:00:00 2001 From: Matthias Wessendorf Date: Thu, 30 Jan 2014 12:02:04 +0100 Subject: [PATCH 4/6] Updating documentation URL link --- server/src/main/webapp/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/webapp/index.html b/server/src/main/webapp/index.html index edca780a18..f55361ff64 100755 --- a/server/src/main/webapp/index.html +++ b/server/src/main/webapp/index.html @@ -38,7 +38,7 @@

Your Keycloak is running.

-

Documentation | Administration Console

+

Documentation | Administration Console

Keycloak Project | Mailing List | From ac6ed6c0205dc8b715284a44f4cd7386a3ab4a60 Mon Sep 17 00:00:00 2001 From: Karel Piwko Date: Thu, 30 Jan 2014 13:04:21 +0100 Subject: [PATCH 5/6] Adding EAP6 notes to documentation section 3.3.2 --- docbook/reference/en/en-US/modules/server-installation.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docbook/reference/en/en-US/modules/server-installation.xml b/docbook/reference/en/en-US/modules/server-installation.xml index 81ca2b42a7..80e7709b31 100755 --- a/docbook/reference/en/en-US/modules/server-installation.xml +++ b/docbook/reference/en/en-US/modules/server-installation.xml @@ -336,10 +336,10 @@ $ keytool -import -alias yourdomain -keystore keycloak.jks -file your-certificat

- Installing the keystore to JBoss AS7 + Installing the keystore to JBoss EAP6/AS7 Now that you have a Java keystore with the appropriate certificates, you need to configure your - JBoss AS7 installation to use it. First step is to move the keystore file to a directory + JBoss EAP6/AS7 installation to use it. First step is to move the keystore file to a directory you can reference in configuration. I like to put it in standalone/configuration. Then you need to edit standalone/configuration/standalone.xml to enable SSL/HTTPS. From c7112dc19f4df7529e0ba6751d8382254b9fcaf4 Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Thu, 30 Jan 2014 09:05:27 -0500 Subject: [PATCH 6/6] KEYCLOAK-274 Compact JSON String. Get resource name from DMR. --- .../KeycloakAdapterConfigDeploymentProcessor.java | 5 +---- .../subsystem/extension/KeycloakAdapterConfigService.java | 7 ++----- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java index 17d73841d7..40eb086ccb 100644 --- a/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigDeploymentProcessor.java @@ -58,10 +58,7 @@ public class KeycloakAdapterConfigDeploymentProcessor implements DeploymentUnitP DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit(); WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY); - //TODO: Find context root properly - String resourceName = deploymentName.substring(0, deploymentName.lastIndexOf('.')); - - addJSONData(service.getJSON(deploymentName, resourceName), warMetaData); + addJSONData(service.getJSON(deploymentName), warMetaData); //addJSONData(getJSON(), warMetaData); } diff --git a/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java index 11f71b2dac..a39fe26be5 100644 --- a/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java +++ b/subsystem/src/main/java/org/keycloak/subsystem/extension/KeycloakAdapterConfigService.java @@ -161,21 +161,18 @@ public class KeycloakAdapterConfigService implements Service