This is an automated email from the ASF dual-hosted git repository.
vavrtom pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-broker-j.git
The following commit(s) were added to refs/heads/main by this push:
new 6390ef17a1 QPID-8583: [Broker-J] Privacy Violation: Heap Inspection
(#124)
6390ef17a1 is described below
commit 6390ef17a138abe4c6ec4a742472640d0d7b02bc
Author: Daniil Kirilyuk <[email protected]>
AuthorDate: Wed Apr 20 09:37:05 2022 +0200
QPID-8583: [Broker-J] Privacy Violation: Heap Inspection (#124)
* QPID-8583: [Broker-J] Privacy Violation: Heap Inspection
* QPID-8583: [Broker-J] Added new line to the end of ClearableCharSequence
Co-authored-by: vavrtom <[email protected]>
---
.../model/ConfiguredDerivedInjectedAttribute.java | 10 +-
.../model/ConfiguredObjectMethodAttribute.java | 19 +-
.../model/ConfiguredSettableInjectedAttribute.java | 11 +-
.../AbstractScramAuthenticationManager.java | 4 +
.../crammd5/CramMd5Base64HashedNegotiator.java | 15 +-
.../sasl/crammd5/CramMd5Base64HexNegotiator.java | 17 +-
.../qpid/server/util/ClearableCharSequence.java | 68 +++
.../java/org/apache/qpid/server/util/Strings.java | 522 ++++++++++++---------
.../org/apache/qpid/server/util/StringsTest.java | 82 ++++
.../auth/BasicAuthPreemptiveAuthenticator.java | 43 +-
10 files changed, 536 insertions(+), 255 deletions(-)
diff --git
a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredDerivedInjectedAttribute.java
b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredDerivedInjectedAttribute.java
index ff88d7ea38..3334ffab02 100644
---
a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredDerivedInjectedAttribute.java
+++
b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredDerivedInjectedAttribute.java
@@ -28,7 +28,9 @@ import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.apache.qpid.server.util.ClearableCharSequence;
import org.apache.qpid.server.util.ServerScopedRuntimeException;
+import org.apache.qpid.server.util.Strings;
public class ConfiguredDerivedInjectedAttribute<C extends ConfiguredObject, T>
extends ConfiguredObjectInjectedAttributeOrStatistic<C, T> implements
ConfiguredObjectInjectedAttribute<C, T>
@@ -157,9 +159,11 @@ public class ConfiguredDerivedInjectedAttribute<C extends
ConfiguredObject, T>
@Override
public boolean isSecureValue(final Object value)
{
- Pattern filter;
- return isSecure() &&
- ((filter = getSecureValueFilter()) == null ||
filter.matcher(String.valueOf(value)).matches());
+ try (final ClearableCharSequence charSequence =
Strings.toCharSequence(value))
+ {
+ final Pattern filter = getSecureValueFilter();
+ return isSecure() && (filter == null ||
filter.matcher(charSequence).matches());
+ }
}
diff --git
a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectMethodAttribute.java
b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectMethodAttribute.java
index dec0c526d2..b137f08359 100644
---
a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectMethodAttribute.java
+++
b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredObjectMethodAttribute.java
@@ -23,6 +23,9 @@ package org.apache.qpid.server.model;
import java.lang.reflect.Method;
import java.util.regex.Pattern;
+import org.apache.qpid.server.util.ClearableCharSequence;
+import org.apache.qpid.server.util.Strings;
+
public abstract class ConfiguredObjectMethodAttribute<C extends
ConfiguredObject, T>
extends ConfiguredObjectMethodAttributeOrStatistic<C,T>
implements ConfiguredObjectAttribute<C,T>
@@ -38,21 +41,13 @@ public abstract class ConfiguredObjectMethodAttribute<C
extends ConfiguredObject
}
@Override
- public boolean isSecureValue(Object value)
+ public boolean isSecureValue(final Object value)
{
- if (isSecure())
+ try (final ClearableCharSequence charSequence =
Strings.toCharSequence(value))
{
- Pattern filter = getSecureValueFilter();
- if (filter == null)
- {
- return true;
- }
- else
- {
- return filter.matcher(String.valueOf(value)).matches();
- }
+ final Pattern filter = getSecureValueFilter();
+ return isSecure() && (filter == null ||
filter.matcher(charSequence).matches());
}
- return false;
}
}
diff --git
a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredSettableInjectedAttribute.java
b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredSettableInjectedAttribute.java
index 0361b023fc..d09ed2ecd0 100644
---
a/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredSettableInjectedAttribute.java
+++
b/broker-core/src/main/java/org/apache/qpid/server/model/ConfiguredSettableInjectedAttribute.java
@@ -35,6 +35,9 @@ import java.util.regex.Pattern;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.apache.qpid.server.util.ClearableCharSequence;
+import org.apache.qpid.server.util.Strings;
+
public class ConfiguredSettableInjectedAttribute<C extends ConfiguredObject, T>
extends ConfiguredObjectInjectedAttributeOrStatistic<C,T> implements
ConfiguredSettableAttribute<C,T>, ConfiguredObjectInjectedAttribute<C,T>
{
@@ -229,9 +232,11 @@ public class ConfiguredSettableInjectedAttribute<C extends
ConfiguredObject, T>
@Override
public boolean isSecureValue(final Object value)
{
- Pattern filter;
- return isSecure() &&
- ((filter = getSecureValueFilter()) == null ||
filter.matcher(String.valueOf(value)).matches());
+ try (final ClearableCharSequence charSequence =
Strings.toCharSequence(value))
+ {
+ final Pattern filter = getSecureValueFilter();
+ return isSecure() && (filter == null ||
filter.matcher(charSequence).matches());
+ }
}
@Override
diff --git
a/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractScramAuthenticationManager.java
b/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractScramAuthenticationManager.java
index abc6cbf7b9..ce433073f1 100644
---
a/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractScramAuthenticationManager.java
+++
b/broker-core/src/main/java/org/apache/qpid/server/security/auth/manager/AbstractScramAuthenticationManager.java
@@ -181,6 +181,10 @@ public abstract class AbstractScramAuthenticationManager<X
extends AbstractScram
{
throw new IllegalArgumentException(e);
}
+ finally
+ {
+ Strings.clearByteArray(saltedPassword);
+ }
}
else if (passwordFields.length == 4)
{
diff --git
a/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CramMd5Base64HashedNegotiator.java
b/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CramMd5Base64HashedNegotiator.java
index 91c9daa1e6..67e450daec 100644
---
a/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CramMd5Base64HashedNegotiator.java
+++
b/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CramMd5Base64HashedNegotiator.java
@@ -29,13 +29,20 @@ public class CramMd5Base64HashedNegotiator extends
AbstractCramMd5Negotiator
public static final String MECHANISM = "CRAM-MD5-HASHED";
private static final PasswordTransformer BASE64_PASSWORD_TRANSFORMER =
passwordData ->
{
- final byte[] passwordBytes = Strings.decodePrivateBase64(new
String(passwordData), "CRAM MD5 hashed password");
+ final byte[] passwordBytes = Strings.decodeCharArray(passwordData,
"CRAM MD5 hashed password");
final char[] password = new char[passwordBytes.length];
- for (int i = 0; i < passwordBytes.length; i++)
+ try
{
- password[i] = (char) passwordBytes[i];
+ for (int i = 0; i < passwordBytes.length; i++)
+ {
+ password[i] = (char) passwordBytes[i];
+ }
+ return password;
+ }
+ finally
+ {
+ Strings.clearByteArray(passwordBytes);
}
- return password;
};
public CramMd5Base64HashedNegotiator(final
PasswordCredentialManagingAuthenticationProvider<?> authenticationProvider,
diff --git
a/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CramMd5Base64HexNegotiator.java
b/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CramMd5Base64HexNegotiator.java
index edcbff548f..39aa6254c6 100644
---
a/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CramMd5Base64HexNegotiator.java
+++
b/broker-core/src/main/java/org/apache/qpid/server/security/auth/sasl/crammd5/CramMd5Base64HexNegotiator.java
@@ -31,14 +31,21 @@ public class CramMd5Base64HexNegotiator extends
AbstractCramMd5Negotiator
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c',
'd', 'e', 'f'};
private static final PasswordTransformer BASE64_HEX_PASSWORD_TRANSFORMER =
passwordData ->
{
- final byte[] passwordBytes = Strings.decodePrivateBase64(new
String(passwordData), "CRAM MD5 hex password");
+ final byte[] passwordBytes = Strings.decodeCharArray(passwordData,
"CRAM MD5 hex password");
final char[] password = new char[passwordBytes.length * 2];
- for (int i = 0; i < passwordBytes.length; i++)
+ try
{
- password[2 * i] = HEX_CHARACTERS[(((int) passwordBytes[i]) & 0xf0)
>> 4];
- password[(2 * i) + 1] = HEX_CHARACTERS[(((int) passwordBytes[i]) &
0x0f)];
+ for (int i = 0; i < passwordBytes.length; i++)
+ {
+ password[2 * i] = HEX_CHARACTERS[(((int) passwordBytes[i]) &
0xf0) >> 4];
+ password[(2 * i) + 1] = HEX_CHARACTERS[(((int)
passwordBytes[i]) & 0x0f)];
+ }
+ return password;
+ }
+ finally
+ {
+ Strings.clearByteArray(passwordBytes);
}
- return password;
};
public CramMd5Base64HexNegotiator(final
PasswordCredentialManagingAuthenticationProvider<?> authenticationProvider,
diff --git
a/broker-core/src/main/java/org/apache/qpid/server/util/ClearableCharSequence.java
b/broker-core/src/main/java/org/apache/qpid/server/util/ClearableCharSequence.java
new file mode 100644
index 0000000000..c8365e2009
--- /dev/null
+++
b/broker-core/src/main/java/org/apache/qpid/server/util/ClearableCharSequence.java
@@ -0,0 +1,68 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.util;
+
+/**
+ * CharSequence backed up by a StringBuilder and implementing AutoCloseable.
+ * On close() StringBuilder is cleared.
+ */
+public class ClearableCharSequence implements CharSequence, AutoCloseable
+{
+ private StringBuilder _stringBuilder = new StringBuilder();
+
+ public ClearableCharSequence(final Object object)
+ {
+ _stringBuilder.append(object == null ? "null".toCharArray() :
object.toString().toCharArray());
+ }
+
+ @Override
+ public void close()
+ {
+ final int length = _stringBuilder.length();
+ _stringBuilder.setLength(0);
+ _stringBuilder.setLength(length);
+ _stringBuilder = null;
+ }
+
+ @Override
+ public int length()
+ {
+ return _stringBuilder.length();
+ }
+
+ @Override
+ public char charAt(final int index)
+ {
+ return _stringBuilder.charAt(index);
+ }
+
+ @Override
+ public CharSequence subSequence(final int start, final int end)
+ {
+ return _stringBuilder.subSequence(start, end);
+ }
+
+ @Override
+ public String toString()
+ {
+ return _stringBuilder.toString();
+ }
+}
diff --git a/broker-core/src/main/java/org/apache/qpid/server/util/Strings.java
b/broker-core/src/main/java/org/apache/qpid/server/util/Strings.java
index b62fe7f4bc..1d48b7fcdf 100644
--- a/broker-core/src/main/java/org/apache/qpid/server/util/Strings.java
+++ b/broker-core/src/main/java/org/apache/qpid/server/util/Strings.java
@@ -20,8 +20,9 @@
*/
package org.apache.qpid.server.util;
-import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
@@ -29,36 +30,93 @@ import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
-import java.util.Properties;
+import java.util.Objects;
import java.util.Set;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-
/**
- * Strings
- *
+ * String utilities
*/
-
public final class Strings
{
+ /**
+ * Utility class shouldn't be instantiated directly
+ */
private Strings()
{
+
}
+ /**
+ * Empty byte array
+ */
private static final byte[] EMPTY = new byte[0];
- private static final ThreadLocal<char[]> charbuf = new
ThreadLocal<char[]>()
+ /**
+ * Thread bound character buffer
+ */
+ private static final ThreadLocal<char[]> CHAR_BUFFER =
ThreadLocal.withInitial(() -> new char[4096]);
+
+ /**
+ * Variable regexp pattern
+ */
+ private static final Pattern VAR =
Pattern.compile("\\$\\{([^}]*)}|\\$(\\$)");
+
+ /**
+ * Null resolver, always returning null
+ */
+ private static final Resolver NULL_RESOLVER = (variable, resolver) -> null;
+
+ /**
+ * Environment variable resolver
+ */
+ public static final Resolver ENV_VARS_RESOLVER = (variable, resolver) ->
System.getenv(variable);
+
+ /**
+ * System property resolver
+ */
+ public static final Resolver JAVA_SYS_PROPS_RESOLVER = (variable,
resolver) -> System.getProperty(variable);
+
+ /**
+ * System resolver chaining environment variable and system property
resolvers
+ */
+ public static final Resolver SYSTEM_RESOLVER =
chain(JAVA_SYS_PROPS_RESOLVER, ENV_VARS_RESOLVER);
+
+ /**
+ * Chains several resolvers into a ChainedResolver instance
+ *
+ * @param resolvers Resolvers to be chained
+ *
+ * @return Resulting ChainedResolver
+ */
+ public static Resolver chain(final Resolver... resolvers)
{
- @Override
- public char[] initialValue()
+ Resolver resolver;
+ if(resolvers.length == 0)
{
- return new char[4096];
+ resolver = NULL_RESOLVER;
}
- };
+ else
+ {
+ resolver = resolvers[resolvers.length - 1];
+ for (int i = resolvers.length - 2; i >= 0; i--)
+ {
+ resolver = new ChainedResolver(resolvers[i], resolver);
+ }
+ }
+ return resolver;
+ }
- public static final byte[] toUTF8(String str)
+ /**
+ * Converts string to the UTF8 encoded byte array
+ *
+ * @param str Source string
+ *
+ * @return Byte array
+ */
+ public static byte[] toUTF8(final String str)
{
if (str == null)
{
@@ -67,11 +125,11 @@ public final class Strings
else
{
final int size = str.length();
- char[] chars = charbuf.get();
+ char[] chars = CHAR_BUFFER.get();
if (size > chars.length)
{
chars = new char[Math.max(size, 2*chars.length)];
- charbuf.set(chars);
+ CHAR_BUFFER.set(chars);
}
str.getChars(0, size, chars, 0);
@@ -80,55 +138,23 @@ public final class Strings
{
if (chars[i] > 127)
{
- try
- {
- return str.getBytes("UTF-8");
- }
- catch (UnsupportedEncodingException e)
- {
- throw new RuntimeException(e);
- }
+ return str.getBytes(StandardCharsets.UTF_8);
}
-
bytes[i] = (byte) chars[i];
}
return bytes;
}
}
- public static final String fromUTF8(byte[] bytes)
- {
- try
- {
- return new String(bytes, "UTF-8");
- }
- catch (UnsupportedEncodingException e)
- {
- throw new RuntimeException(e);
- }
- }
-
- private static final Pattern VAR =
Pattern.compile("(?:\\$\\{([^\\}]*)\\})|(?:\\$(\\$))");
-
- public static Resolver chain(Resolver... resolvers)
- {
- Resolver resolver;
- if(resolvers.length == 0)
- {
- resolver = NULL_RESOLVER;
- }
- else
- {
- resolver = resolvers[resolvers.length - 1];
- for (int i = resolvers.length - 2; i >= 0; i--)
- {
- resolver = new ChainedResolver(resolvers[i], resolver);
- }
- }
- return resolver;
- }
-
- public static byte[] decodePrivateBase64(String base64String, String
description)
+ /**
+ * Decodes base64 encoded string into a byte array
+ *
+ * @param base64String Base64 encoded string
+ * @param description String description provided for logging purposes
+ *
+ * @return Resulting byte array
+ */
+ public static byte[] decodePrivateBase64(final String base64String, final
String description)
{
if (isInvalidBase64String(base64String))
{
@@ -136,148 +162,151 @@ public final class Strings
throw new IllegalArgumentException("Cannot convert " + description
+
" string to a byte[] - it does not appear to be base64
data");
}
-
- return Base64.getDecoder().decode(base64String);
- }
-
- public static byte[] decodeBase64(String base64String)
- {
- if (isInvalidBase64String(base64String))
- {
- throw new IllegalArgumentException("Cannot convert string '" +
base64String +
- "' to a byte[] - it does not appear to be base64 data");
- }
-
return Base64.getDecoder().decode(base64String);
}
- private static boolean isInvalidBase64String(String base64String)
- {
- return !base64String.replaceAll("\\s",
"").matches("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$");
- }
-
- public static interface Resolver
- {
- String resolve(String variable, final Resolver resolver);
- }
-
- private static final Resolver NULL_RESOLVER =
- new Resolver()
- {
- @Override
- public String resolve(final String variable, final Resolver
resolver)
- {
- return null;
- }
- };
-
- public static class MapResolver implements Resolver
+ /**
+ * Decodes base64 encoded char array into a byte array
+ *
+ * @param base64Chars Base64 encoded char array
+ * @param description String description provided for logging purposes
+ *
+ * @return Resulting byte array
+ */
+ public static byte[] decodeCharArray(final char[] base64Chars, final
String description)
{
-
- private final Map<String,String> map;
-
- public MapResolver(Map<String,String> map)
- {
- this.map = map;
- }
-
- @Override
- public String resolve(String variable, final Resolver resolver)
+ if (base64Chars == null)
{
- return map.get(variable);
+ return null;
}
- }
-
- public static class PropertiesResolver implements Resolver
- {
-
- private final Properties properties;
-
- public PropertiesResolver(Properties properties)
+ try
{
- this.properties = properties;
+ final CharBuffer charBuffer = CharBuffer.wrap(base64Chars);
+ final ByteBuffer byteBuffer =
StandardCharsets.UTF_8.encode(charBuffer);
+ return Base64.getDecoder().decode(byteBuffer).array();
}
-
- @Override
- public String resolve(String variable, final Resolver resolver)
+ catch (IllegalArgumentException e)
{
- return properties.getProperty(variable);
+ // do not add base64String to exception message as it can contain
private data
+ throw new IllegalArgumentException("Cannot convert "
+ + description
+ + " string to a byte[] - it
does not appear to be base64 data");
}
}
- public static class ChainedResolver implements Resolver
+ /**
+ * Fills byte arrays with blank characters
+ *
+ * @param bytes Byte arrays to be cleared
+ */
+ public static void clearByteArray(byte[]... bytes)
{
- private final Resolver primary;
- private final Resolver secondary;
-
- public ChainedResolver(Resolver primary, Resolver secondary)
+ for (final byte[] array : bytes)
{
- this.primary = primary;
- this.secondary = secondary;
- }
-
- @Override
- public String resolve(String variable, final Resolver resolver)
- {
- String result = primary.resolve(variable, resolver);
- if (result == null)
+ if (array != null)
{
- result = secondary.resolve(variable, resolver);
+ Arrays.fill(array, (byte) 0);
}
- return result;
}
}
- public static final Resolver ENV_VARS_RESOLVER = new Resolver()
- {
- @Override
- public String resolve(final String variable, final Resolver
resolver)
- {
- return System.getenv(variable);
- }
- };
-
+ /**
+ * Converts an object to the ClearableCharSequence
+ *
+ * @param object Object to convert
+ *
+ * @return ClearableCharSequence instance
+ */
+ public static ClearableCharSequence toCharSequence(final Object object)
+ {
+ return new ClearableCharSequence(object);
+ }
- public static final Resolver JAVA_SYS_PROPS_RESOLVER = new Resolver()
+ /**
+ * Decodes base64 encoded string into a byte array
+ *
+ * @param base64String Base64 encoded string
+ *
+ * @return Resulting byte array
+ */
+ public static byte[] decodeBase64(final String base64String)
{
- @Override
- public String resolve(final String variable, final Resolver resolver)
+ if (isInvalidBase64String(base64String))
{
- return System.getProperty(variable);
+ throw new IllegalArgumentException("Cannot convert string '" +
base64String +
+ "' to a byte[] - it does not appear to be base64 data");
}
- };
-
-
- public static final Resolver SYSTEM_RESOLVER =
chain(JAVA_SYS_PROPS_RESOLVER, ENV_VARS_RESOLVER);
+ return Base64.getDecoder().decode(base64String);
+ }
- public static final String expand(String input)
+ /**
+ * Checks if string is valid base64 encoded string or not
+ *
+ * @param base64String Base64 encoded string
+ *
+ * @return True when parameter passed is valid base64 encoded string,
false otherwise
+ */
+ private static boolean isInvalidBase64String(final String base64String)
{
- return expand(input, SYSTEM_RESOLVER);
+ return !base64String.replaceAll("\\s",
"").matches("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$");
}
- public static final String expand(String input, Resolver resolver)
+ /**
+ * Expands string replacing variables it contains with variable values
+ *
+ * @param input Source string
+ * @param resolver Resolver to use
+ *
+ * @return Expanded string
+ */
+ public static String expand(final String input, final Resolver resolver)
{
- return expand(input, resolver, new Stack<String>(),true);
+ return expand(input, resolver, new Stack<>(),true);
}
- public static final String expand(String input, boolean failOnUnresolved,
Resolver... resolvers)
+
+ /**
+ * Expands string replacing variables it contains with variable values
+ *
+ * @param input Source string
+ * @param failOnUnresolved Boolean flag defining if an exception should be
thrown in case of failed resolution
+ * @param resolvers Resolvers to use
+ *
+ * @return Expanded string
+ */
+ public static String expand(final String input, final boolean
failOnUnresolved, final Resolver... resolvers)
{
- return expand(input, chain(resolvers), new Stack<String>(),
failOnUnresolved);
+ return expand(input, chain(resolvers), new Stack<>(),
failOnUnresolved);
}
- private static final String expand(String input, Resolver resolver,
Stack<String> stack, boolean failOnUnresolved)
+ /**
+ * Expands string replacing variables it contains with variable values
+ *
+ * @param input Source string
+ * @param resolver Resolver
+ * @param stack Stack containing variable chain
+ * @param failOnUnresolved Boolean flag defining if an exception should be
thrown in case of failed resolution
+ *
+ * @return Expanded string
+ */
+ private static String expand(
+ final String input,
+ final Resolver resolver,
+ final Stack<String> stack,
+ final boolean failOnUnresolved
+ )
{
if (input == null)
{
return null;
}
- Matcher m = VAR.matcher(input);
- StringBuffer result = new StringBuffer();
+ final Matcher m = VAR.matcher(input);
+ final StringBuffer result = new StringBuffer();
while (m.find())
{
- String var = m.group(1);
+ final String var = m.group(1);
if (var == null)
{
- String esc = m.group(2);
+ final String esc = m.group(2);
if ("$".equals(esc))
{
m.appendReplacement(result, Matcher.quoteReplacement("$"));
@@ -296,10 +325,22 @@ public final class Strings
return result.toString();
}
- private static final String resolve(String var,
- Resolver resolver,
- Stack<String> stack,
- final boolean failOnUnresolved)
+ /**
+ * Resolves variable
+ *
+ * @param var Variable name
+ * @param resolver Resolver
+ * @param stack Stack containing variable chain
+ * @param failOnUnresolved Boolean flag defining if an exception should be
thrown in case of failed resolution
+ *
+ * @return Resolved variable value
+ */
+ private static String resolve(
+ final String var,
+ final Resolver resolver,
+ final Stack<String> stack,
+ final boolean failOnUnresolved
+ )
{
if (stack.contains(var))
{
@@ -308,10 +349,10 @@ public final class Strings
stack));
}
- String result = resolver.resolve(var, resolver);
+ final String result = resolver.resolve(var, resolver);
if (result == null)
{
- if(failOnUnresolved)
+ if (failOnUnresolved)
{
throw new IllegalArgumentException("no such variable: " + var);
}
@@ -332,57 +373,60 @@ public final class Strings
}
}
- public static final String join(String sep, Iterable items)
+ /**
+ * Joins string representation of an object iterable
+ *
+ * @param sep Separator
+ * @param items Object iterable
+ *
+ * @return Resulting string
+ */
+ public static String join(final String sep, final Iterable<?> items)
{
- StringBuilder result = new StringBuilder();
-
- for (Object o : items)
+ Objects.requireNonNull(sep, "Separator must be not null");
+ Objects.requireNonNull(items, "Items must be not null");
+ final StringBuilder result = new StringBuilder();
+ for (final Object object : items)
{
if (result.length() > 0)
{
result.append(sep);
}
- result.append(o.toString());
+ result.append(object == null ? "null" : object.toString());
}
-
return result.toString();
}
- public static final String join(String sep, Object[] items)
+ /**
+ * Joins string representation of an object array
+ *
+ * @param sep Separator
+ * @param items Object array
+ *
+ * @return Resulting string
+ */
+ public static String join(final String sep, final Object[] items)
{
+ Objects.requireNonNull(items, "Items must be not null");
return join(sep, Arrays.asList(items));
}
- public static final List<String> split(String listAsString)
+ /**
+ * Splits source string into a liast of tokens separated by comma
+ *
+ * @param listAsString Source string
+ *
+ * @return List of tokens
+ */
+ public static List<String> split(final String listAsString)
{
- if(listAsString != null && !"".equals(listAsString))
+ if (listAsString != null && !"".equals(listAsString))
{
return Arrays.asList(listAsString.split("\\s*,\\s*"));
}
return Collections.emptyList();
}
- public static String printMap(Map<String,Object> map)
- {
- StringBuilder sb = new StringBuilder();
- sb.append("<");
- if (map != null)
- {
- for(Map.Entry<String,Object> entry : map.entrySet())
- {
- sb.append(entry.getKey()).append(" =
").append(entry.getValue()).append(" ");
- }
- }
- sb.append(">");
- return sb.toString();
- }
-
-
- public static Resolver createSubstitutionResolver(String prefix,
LinkedHashMap<String,String> substitutions)
- {
- return new StringSubstitutionResolver(prefix, substitutions);
- }
-
/**
* Dumps bytes in the textual format used by UNIX od(1) in hex (x4) mode
i.e. {@code od -Ax -tx1 -v}.
*
@@ -391,11 +435,11 @@ public final class Strings
*
* @param buf - buffer to be dumped. Buffer will be unchanged.
*/
- public static String hexDump(ByteBuffer buf)
+ public static String hexDump(final ByteBuffer buf)
{
- StringBuilder builder = new StringBuilder();
+ final StringBuilder builder = new StringBuilder();
int count = 0;
- for(int p = buf.position(); p < buf.position() + buf.remaining(); p++)
+ for (int p = buf.position(); p < buf.position() + buf.remaining(); p++)
{
if (count % 16 == 0)
{
@@ -414,15 +458,80 @@ public final class Strings
return builder.toString();
}
- private static class StringSubstitutionResolver implements Resolver
+ /**
+ * Creates substitution resolver
+ *
+ * @param prefix Substitution prefix
+ * @param substitutions Map of substituitions
+ *
+ * @return StringSubstitutionResolver
+ */
+ public static Resolver createSubstitutionResolver(final String prefix,
final LinkedHashMap<String, String> substitutions)
+ {
+ return new StringSubstitutionResolver(prefix, substitutions);
+ }
+
+ /**
+ * Resolver variable using supplied resolver
+ */
+ public interface Resolver
{
+ String resolve(final String variable, final Resolver resolver);
+ }
- private final ThreadLocal<Set<String>> _stack = new ThreadLocal<>();
+ /**
+ * Resolves variable from a map.
+ */
+ public static class MapResolver implements Resolver
+ {
+ private final Map<String,String> map;
+
+ public MapResolver(final Map<String,String> map)
+ {
+ this.map = map;
+ }
+ @Override
+ public String resolve(final String variable, final Resolver resolver)
+ {
+ return map.get(variable);
+ }
+ }
+
+ /**
+ * Chains two resolvers trying to resolve variable against first one and
if unsuccessful against second one
+ */
+ public static class ChainedResolver implements Resolver
+ {
+ private final Resolver primary;
+ private final Resolver secondary;
+
+ public ChainedResolver(final Resolver primary, final Resolver
secondary)
+ {
+ this.primary = primary;
+ this.secondary = secondary;
+ }
+
+ @Override
+ public String resolve(final String variable, final Resolver resolver)
+ {
+ final String result = primary.resolve(variable, resolver);
+ return result != null
+ ? result
+ : secondary.resolve(variable, resolver);
+ }
+ }
+
+ /**
+ * Resolves substituted variables
+ */
+ private static class StringSubstitutionResolver implements Resolver
+ {
+ private final ThreadLocal<Set<String>> _stack = new ThreadLocal<>();
private final LinkedHashMap<String, String> _substitutions;
private final String _prefix;
- private StringSubstitutionResolver(String prefix,
LinkedHashMap<String, String> substitutions)
+ private StringSubstitutionResolver(final String prefix, final
LinkedHashMap<String, String> substitutions)
{
_prefix = prefix;
_substitutions = substitutions;
@@ -433,22 +542,18 @@ public final class Strings
{
boolean clearStack = false;
Set<String> currentStack = _stack.get();
- if(currentStack == null)
+ if (currentStack == null)
{
currentStack = new HashSet<>();
_stack.set(currentStack);
clearStack = true;
}
-
try
{
- if(currentStack.contains(variable))
+ if (currentStack.contains(variable))
{
throw new IllegalArgumentException("The value of attribute
" + variable + " is defined recursively");
-
}
-
-
if (variable.startsWith(_prefix))
{
currentStack.add(variable);
@@ -457,9 +562,9 @@ public final class Strings
String expanded = Strings.expand("${" +
variable.substring(_prefix.length()) + "}", resolver,
stack, false);
currentStack.remove(variable);
- if(expanded != null)
+ if (expanded != null)
{
- for(Map.Entry<String,String> entry :
_substitutions.entrySet())
+ for (final Map.Entry<String,String> entry :
_substitutions.entrySet())
{
expanded = expanded.replace(entry.getKey(),
entry.getValue());
}
@@ -474,8 +579,7 @@ public final class Strings
}
finally
{
-
- if(clearStack)
+ if (clearStack)
{
_stack.remove();
}
diff --git
a/broker-core/src/test/java/org/apache/qpid/server/util/StringsTest.java
b/broker-core/src/test/java/org/apache/qpid/server/util/StringsTest.java
index de0b88c4da..33bab79c41 100644
--- a/broker-core/src/test/java/org/apache/qpid/server/util/StringsTest.java
+++ b/broker-core/src/test/java/org/apache/qpid/server/util/StringsTest.java
@@ -26,6 +26,10 @@ import static org.junit.Assert.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat;
import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
@@ -100,4 +104,82 @@ public class StringsTest extends UnitTestBase
String actual =
Strings.hexDump(ByteBuffer.wrap("12345678123456789".getBytes()));
assertThat(actual, is(equalTo(expected)));
}
+
+ @Test
+ public void toCharSequence()
+ {
+ CharSequence expected = "null";
+ CharSequence actual = Strings.toCharSequence(null).toString();
+ assertThat(expected, is(equalTo(actual)));
+
+ expected = "";
+ actual = Strings.toCharSequence("").toString();
+ assertThat(expected, is(equalTo(actual)));
+
+ Object object = new Object();
+ expected = object.toString();
+ actual = Strings.toCharSequence(object).toString();
+ assertThat(expected, is(equalTo(actual)));
+ }
+
+ @Test
+ public void decodeCharArray()
+ {
+ assertThat(null, is(equalTo(Strings.decodeCharArray(null, null))));
+ assertThat(new byte[]{}, is(equalTo(Strings.decodeCharArray(new
char[]{}, null))));
+ assertThat(new byte[]{},
is(equalTo(Strings.decodeCharArray("".toCharArray(), null))));
+
+ final char[] base64 =
Base64.getEncoder().encodeToString("test".getBytes(StandardCharsets.UTF_8)).toCharArray();
+ assertThat(new byte[]{116, 101, 115, 116},
is(equalTo(Strings.decodeCharArray(base64, null))));
+ }
+
+ @Test
+ public void split()
+ {
+ assertThat(Collections.emptyList(), is(equalTo(Strings.split(null))));
+ assertThat(Collections.emptyList(), is(equalTo(Strings.split(""))));
+ assertThat(Collections.singletonList("a"),
is(equalTo(Strings.split("a"))));
+ assertThat(Collections.singletonList("a "),
is(equalTo(Strings.split("a "))));
+ assertThat(Arrays.asList("a", "b"), is(equalTo(Strings.split("a,b"))));
+ }
+
+ @Test
+ public void join()
+ {
+ try
+ {
+ Strings.join(",", (Object[]) null);
+ }
+ catch (NullPointerException e)
+ {
+ assertThat("Items must be not null", is(equalTo(e.getMessage())));
+ }
+
+ try
+ {
+ Strings.join(",", (Iterable<?>) null);
+ }
+ catch (NullPointerException e)
+ {
+ assertThat("Items must be not null", is(equalTo(e.getMessage())));
+ }
+
+ try
+ {
+ Strings.join(null, (Iterable<?>) null);
+ }
+ catch (NullPointerException e)
+ {
+ assertThat("Separator must be not null",
is(equalTo(e.getMessage())));
+ }
+
+ assertThat("", is(equalTo(Strings.join(",", new Object[]{}))));
+ assertThat("", is(equalTo(Strings.join(",", new ArrayList<>()))));
+
+ assertThat("a,b,c", is(equalTo(Strings.join(",", new Object[]{"a",
"b", "c"}))));
+ assertThat("a,b,c", is(equalTo(Strings.join(",", Arrays.asList("a",
"b", "c")))));
+
+ assertThat("a,null,1", is(equalTo(Strings.join(",", new Object[]{"a",
null, 1}))));
+ assertThat("a,null,1", is(equalTo(Strings.join(",", Arrays.asList("a",
null, 1)))));
+ }
}
diff --git
a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/auth/BasicAuthPreemptiveAuthenticator.java
b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/auth/BasicAuthPreemptiveAuthenticator.java
index c79d4bde3d..cd87c21146 100644
---
a/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/auth/BasicAuthPreemptiveAuthenticator.java
+++
b/broker-plugins/management-http/src/main/java/org/apache/qpid/server/management/plugin/auth/BasicAuthPreemptiveAuthenticator.java
@@ -57,28 +57,33 @@ public class BasicAuthPreemptiveAuthenticator implements
HttpRequestPreemptiveAu
final String[] tokens = header.split("\\s");
if (tokens.length >= 2 && "BASIC".equalsIgnoreCase(tokens[0]))
{
- boolean isBasicAuthSupported = false;
- if (request.isSecure())
- {
- isBasicAuthSupported =
managementConfiguration.isHttpsBasicAuthenticationEnabled();
- }
- else
- {
- isBasicAuthSupported =
managementConfiguration.isHttpBasicAuthenticationEnabled();
- }
+ boolean isBasicAuthSupported = request.isSecure()
+ ?
managementConfiguration.isHttpsBasicAuthenticationEnabled()
+ :
managementConfiguration.isHttpBasicAuthenticationEnabled();
if (isBasicAuthSupported)
{
- final String base64UsernameAndPassword = tokens[1];
- final String[] credentials = new
String(Strings.decodePrivateBase64(base64UsernameAndPassword,
- "basic authentication credentials"),
StandardCharsets.UTF_8).split(":", 2);
- if (credentials.length == 2)
+ final byte[] base64EncodedContent =
Strings.decodeCharArray(
+ tokens[1].toCharArray(),
+ "basic authentication credentials"
+ );
+ try
{
- final String username = credentials[0];
- final String password = credentials[1];
- final AuthenticationResult authenticationResult =
namePasswdAuthProvider.authenticate(username, password);
- final SubjectAuthenticationResult result =
subjectCreator.createResultWithGroups(authenticationResult);
-
- return result.getSubject();
+ final String[] decodedHeaderContent =
+ new String(base64EncodedContent,
StandardCharsets.UTF_8).split(":", 2);
+ if (decodedHeaderContent.length == 2)
+ {
+ final String token1 = decodedHeaderContent[0];
+ final String token2 = decodedHeaderContent[1];
+ final AuthenticationResult authenticationResult =
+
namePasswdAuthProvider.authenticate(token1, token2);
+ final SubjectAuthenticationResult result =
+
subjectCreator.createResultWithGroups(authenticationResult);
+ return result.getSubject();
+ }
+ }
+ finally
+ {
+ Strings.clearByteArray(base64EncodedContent);
}
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]