From 3b981b3920dc2214e943d9de81bd991fb7182eb6 Mon Sep 17 00:00:00 2001 From: Gary Tully Date: Tue, 4 Oct 2022 14:04:41 +0100 Subject: [PATCH] ARTEMIS-4028 - add alder32 checksum to the status of properties files read by the broker --- ...ntPropertyBeanIntrospectorWithIgnores.java | 2 +- .../activemq/artemis/api/core/JsonUtil.java | 15 +++ .../artemis/api/core/JsonUtilTest.java | 19 ++++ .../core/config/impl/ConfigurationImpl.java | 93 +++++++++---------- .../core/server/impl/ActiveMQServerImpl.java | 6 +- .../core/server/impl/ServerStatus.java | 56 +++++++---- .../security/jaas/ReloadableProperties.java | 34 ++++++- .../config/impl/ConfigurationImplTest.java | 1 + .../jaas/PropertiesLoginModuleTest.java | 28 ++++++ 9 files changed, 181 insertions(+), 73 deletions(-) diff --git a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/uri/FluentPropertyBeanIntrospectorWithIgnores.java b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/uri/FluentPropertyBeanIntrospectorWithIgnores.java index ca0b99b0a9..e21dddcf09 100644 --- a/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/uri/FluentPropertyBeanIntrospectorWithIgnores.java +++ b/artemis-commons/src/main/java/org/apache/activemq/artemis/utils/uri/FluentPropertyBeanIntrospectorWithIgnores.java @@ -63,7 +63,7 @@ public class FluentPropertyBeanIntrospectorWithIgnores extends FluentPropertyBea pd.setWriteMethod(m); } } catch (IntrospectionException e) { - logger.debug(e.getMessage(), e); + logger.trace("error for property named {}", propertyName, e); } } } diff --git a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/JsonUtil.java b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/JsonUtil.java index bfa7bbce31..8d193412d1 100644 --- a/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/JsonUtil.java +++ b/artemis-core-client/src/main/java/org/apache/activemq/artemis/api/core/JsonUtil.java @@ -409,6 +409,21 @@ public final class JsonUtil { return jsonObjectBuilder.build(); } + // component path, may contain x/y/z as a pointer to a nested object + public static JsonObjectBuilder objectBuilderWithValueAtPath(String componentPath, JsonValue componentStatus) { + JsonObjectBuilder jsonObjectBuilder = JsonLoader.createObjectBuilder(); + String[] nestedComponents = componentPath.split("/", 0); + // may need to nest this status in objects + for (int i = nestedComponents.length - 1; i > 0; i--) { + JsonObjectBuilder nestedBuilder = JsonLoader.createObjectBuilder(); + nestedBuilder.add(nestedComponents[i], componentStatus); + componentStatus = nestedBuilder.build(); + } + jsonObjectBuilder.add(nestedComponents[0], componentStatus); + return jsonObjectBuilder; + } + + private static class NullableJsonString implements JsonValue, JsonString { private final String value; diff --git a/artemis-core-client/src/test/java/org/apache/activemq/artemis/api/core/JsonUtilTest.java b/artemis-core-client/src/test/java/org/apache/activemq/artemis/api/core/JsonUtilTest.java index 7c11ad4534..b2ff4394e4 100644 --- a/artemis-core-client/src/test/java/org/apache/activemq/artemis/api/core/JsonUtilTest.java +++ b/artemis-core-client/src/test/java/org/apache/activemq/artemis/api/core/JsonUtilTest.java @@ -212,4 +212,23 @@ public class JsonUtilTest { Assert.assertEquals(sourceTwo, JsonUtil.mergeAndUpdate(sourceOne, sourceTwo)); } + + @Test + public void testBuilderWithValueAtPath() { + + JsonObjectBuilder jsonObjectBuilder = JsonLoader.createObjectBuilder(); + JsonUtil.addToObject("x", "y", jsonObjectBuilder); + JsonObject target = jsonObjectBuilder.build(); + + JsonObjectBuilder nested = JsonUtil.objectBuilderWithValueAtPath("a/b/c", target); + JsonObject inserted = nested.build(); + Assert.assertTrue(inserted.containsKey("a")); + + Assert.assertEquals(target, inserted.getJsonObject("a").getJsonObject("b").getJsonObject("c")); + + nested = JsonUtil.objectBuilderWithValueAtPath("c", target); + inserted = nested.build(); + Assert.assertTrue(inserted.containsKey("c")); + Assert.assertEquals(target, inserted.getJsonObject("c")); + } } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java index a7116a1e54..c9d349ed1b 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImpl.java @@ -34,6 +34,7 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URI; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.security.AccessController; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; @@ -123,6 +124,9 @@ import org.apache.commons.beanutils.expression.DefaultResolver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.invoke.MethodHandles; +import java.util.zip.Adler32; +import java.util.zip.Checksum; + import org.apache.commons.beanutils.expression.Resolver; public class ConfigurationImpl implements Configuration, Serializable { @@ -411,7 +415,8 @@ public class ConfigurationImpl implements Configuration, Serializable { * Parent folder for all data folders. */ private File artemisInstance; - private transient volatile JsonObject status = JsonLoader.createObjectBuilder().build(); + private transient JsonObject status = JsonLoader.createObjectBuilder().build(); + private final transient Checksum checksum = new Adler32(); @Override public String getJournalRetentionDirectory() { @@ -501,15 +506,16 @@ public class ConfigurationImpl implements Configuration, Serializable { try (FileInputStream fileInputStream = new FileInputStream(new File(dir, fileName)); BufferedInputStream reader = new BufferedInputStream(fileInputStream)) { brokerProperties.clear(); brokerProperties.load(reader); - parsePrefixedProperties(brokerProperties, null); + parsePrefixedProperties(fileName, brokerProperties, null); } } } } } else { - try (FileInputStream fileInputStream = new FileInputStream(fileUrl); BufferedInputStream reader = new BufferedInputStream(fileInputStream)) { + File file = new File(fileUrl); + try (FileInputStream fileInputStream = new FileInputStream(file); BufferedInputStream reader = new BufferedInputStream(fileInputStream)) { brokerProperties.load(reader); - parsePrefixedProperties(brokerProperties, null); + parsePrefixedProperties(file.getName(), brokerProperties, null); } } } @@ -519,9 +525,14 @@ public class ConfigurationImpl implements Configuration, Serializable { } public void parsePrefixedProperties(Properties properties, String prefix) throws Exception { - Map beanProperties = new LinkedHashMap<>(); + parsePrefixedProperties("system", properties, prefix); + } + public void parsePrefixedProperties(String name, Properties properties, String prefix) throws Exception { + Map beanProperties = new LinkedHashMap<>(); + long alder32Hash = 0; synchronized (properties) { + checksum.reset(); String key = null; for (Map.Entry entry : properties.entrySet()) { key = entry.getKey().toString(); @@ -531,53 +542,37 @@ public class ConfigurationImpl implements Configuration, Serializable { } key = entry.getKey().toString().substring(prefix.length()); } - String value = XMLUtil.replaceSystemPropsInString(entry.getValue().toString()); + String value = entry.getValue().toString(); + + checksum.update(key.getBytes(StandardCharsets.UTF_8)); + checksum.update('='); + checksum.update(value.getBytes(StandardCharsets.UTF_8)); + + value = XMLUtil.replaceSystemPropsInString(value); value = PasswordMaskingUtil.resolveMask(isMaskPassword(), value, getPasswordCodec()); key = XMLUtil.replaceSystemPropsInString(key); logger.debug("Property config, {}={}", key, value); beanProperties.put(key, value); } + alder32Hash = checksum.getValue(); } + updateReadPropertiesStatus(name, alder32Hash); if (!beanProperties.isEmpty()) { - populateWithProperties(beanProperties); + populateWithProperties(name, beanProperties); } } - public void populateWithProperties(Map beanProperties) throws InvocationTargetException, IllegalAccessException { + public void populateWithProperties(final String propsId, Map beanProperties) throws InvocationTargetException, IllegalAccessException { CollectionAutoFillPropertiesUtil autoFillCollections = new CollectionAutoFillPropertiesUtil(); BeanUtilsBean beanUtils = new BeanUtilsBean(new ConvertUtilsBean(), autoFillCollections) { // override to treat missing properties as errors, not skip as the default impl does @Override public void setProperty(final Object bean, String name, final Object value) throws InvocationTargetException, IllegalAccessException { { - if (logger.isTraceEnabled()) { - final StringBuilder sb = new StringBuilder(" setProperty("); - sb.append(bean); - sb.append(", "); - sb.append(name); - sb.append(", "); - if (value == null) { - sb.append(""); - } else if (value instanceof String) { - sb.append((String) value); - } else if (value instanceof String[]) { - final String[] values = (String[]) value; - sb.append('['); - for (int i = 0; i < values.length; i++) { - if (i > 0) { - sb.append(','); - } - sb.append(values[i]); - } - sb.append(']'); - } else { - sb.append(value.toString()); - } - sb.append(')'); - logger.trace(sb.toString()); + if (logger.isDebugEnabled()) { + logger.debug("setProperty on {}, name: {}, value: {}", bean.getClass(), name, value); } - // Resolve any nested expression to get the actual target bean Object target = bean; final Resolver resolver = getPropertyUtils().getResolver(); @@ -592,10 +587,7 @@ public class ConfigurationImpl implements Configuration, Serializable { throw new InvocationTargetException(e, "No getter for property:" + name + ", on: " + bean); } } - if (logger.isTraceEnabled()) { - logger.trace(" Target bean = " + target); - logger.trace(" Target name = " + name); - } + logger.trace("resolved target, bean: {}, name: {}", target.getClass(), name); // Declare local variables we will require final String propName = resolver.getProperty(name); // Simple name of target property @@ -746,7 +738,7 @@ public class ConfigurationImpl implements Configuration, Serializable { BeanSupport.customise(beanUtils); - logger.debug("populate:" + this + ", " + beanProperties); + logger.trace("populate: bean: {} with {}", this, beanProperties); HashMap errors = new HashMap<>(); // Loop through the property name/value pairs to be set @@ -757,7 +749,7 @@ public class ConfigurationImpl implements Configuration, Serializable { // Perform the assignment for this property beanUtils.setProperty(this, name, entry.getValue()); } catch (InvocationTargetException invocationTargetException) { - logger.trace("failed to populate property key: " + name, invocationTargetException); + logger.trace("failed to populate property with key: {}", name, invocationTargetException); Throwable toLog = invocationTargetException; if (invocationTargetException.getCause() != null) { toLog = invocationTargetException.getCause(); @@ -768,23 +760,28 @@ public class ConfigurationImpl implements Configuration, Serializable { trackError(errors, entry, oops); } } - updateStatus(errors); + updateApplyStatus(propsId, errors); } private void trackError(HashMap errors, Map.Entry entry, Throwable oops) { - logger.debug("failed to populate property entry(" + entry.toString() + "), reason: " + oops.getLocalizedMessage(), oops); + logger.debug("failed to populate property entry({}), reason: {}", entry, oops); errors.put(entry.toString(), oops.getLocalizedMessage()); } - private void updateStatus(HashMap errors) { - - JsonArrayBuilder errorsObjectArray = JsonLoader.createArrayBuilder(); + private synchronized void updateApplyStatus(String propsId, HashMap errors) { + JsonArrayBuilder errorsObjectArrayBuilder = JsonLoader.createArrayBuilder(); for (Map.Entry entry : errors.entrySet()) { - errorsObjectArray.add(JsonLoader.createObjectBuilder().add("value", entry.getKey()).add("reason", entry.getValue())); + errorsObjectArrayBuilder.add(JsonLoader.createObjectBuilder().add("value", entry.getKey()).add("reason", entry.getValue())); } - JsonObjectBuilder jsonObjectBuilder = JsonLoader.createObjectBuilder(); - jsonObjectBuilder.add("properties", JsonLoader.createObjectBuilder().add("errors", errorsObjectArray)); + JsonObjectBuilder jsonObjectBuilder = + JsonUtil.objectBuilderWithValueAtPath("properties/" + propsId + "/errors", errorsObjectArrayBuilder.build()); + status = JsonUtil.mergeAndUpdate(status, jsonObjectBuilder.build()); + } + private synchronized void updateReadPropertiesStatus(String propsId, long alder32Hash) { + JsonObjectBuilder propertiesReadStatusBuilder = JsonLoader.createObjectBuilder(); + propertiesReadStatusBuilder.add("alder32", String.valueOf(alder32Hash)); + JsonObjectBuilder jsonObjectBuilder = JsonUtil.objectBuilderWithValueAtPath("properties/" + propsId, propertiesReadStatusBuilder.build()); status = JsonUtil.mergeAndUpdate(status, jsonObjectBuilder.build()); } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java index 27b6ba1cc9..a0e499ca13 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ActiveMQServerImpl.java @@ -483,7 +483,7 @@ public class ActiveMQServerImpl implements ActiveMQServer { this.serviceRegistry = serviceRegistry == null ? new ServiceRegistryImpl() : serviceRegistry; - this.serverStatus = new ServerStatus(this); + this.serverStatus = ServerStatus.getInstanceFor(this); } @Override @@ -628,7 +628,7 @@ public class ActiveMQServerImpl implements ActiveMQServer { } configuration.parseProperties(propertiesFileUrl); - updateStatus("configuration", configuration.getStatus()); + updateStatus(ServerStatus.CONFIGURATION_COMPONENT, configuration.getStatus()); initializeExecutorServices(); @@ -4402,7 +4402,7 @@ public class ActiveMQServerImpl implements ActiveMQServer { configurationReloadDeployed.set(false); if (isActive()) { configuration.parseProperties(propertiesFileUrl); - updateStatus("configuration", configuration.getStatus()); + updateStatus(ServerStatus.CONFIGURATION_COMPONENT, configuration.getStatus()); deployReloadableConfigFromConfiguration(); } } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerStatus.java b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerStatus.java index 50fbe77533..8d9c927002 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerStatus.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/core/server/impl/ServerStatus.java @@ -28,41 +28,57 @@ import org.apache.activemq.artemis.utils.JsonLoader; public class ServerStatus { - private final ActiveMQServerImpl server; + public static final String SERVER_COMPONENT = "server"; + public static final String CONFIGURATION_COMPONENT = "configuration"; + public static final String JAAS_COMPONENT = SERVER_COMPONENT + "/jaas"; + + private static final ServerStatus instance = new ServerStatus(); + + public static synchronized ServerStatus getInstanceFor(ActiveMQServerImpl activeMQServer) { + if (instance.server == null) { + instance.server = activeMQServer; + instance.immutableStateValues.put("version", instance.server.getVersion().getFullVersion()); + } + return instance; + } + + public static synchronized ServerStatus getInstance() { + return instance; + } + + private ActiveMQServerImpl server; private final HashMap immutableStateValues = new HashMap<>(); private JsonObject globalStatus = JsonLoader.createObjectBuilder().build(); - public ServerStatus(ActiveMQServerImpl activeMQServer) { - this.server = activeMQServer; - immutableStateValues.put("version", server.getVersion().getFullVersion()); - } - public synchronized String asJson() { updateServerStatus(); return globalStatus.toString(); } private synchronized void updateServerStatus() { - HashMap snapshotOfServerStatusAttributes = new HashMap<>(); - snapshotOfServerStatusAttributes.putAll(immutableStateValues); - snapshotOfServerStatusAttributes.put("identity", server.getIdentity()); - SimpleString nodeId = server.getNodeID(); - snapshotOfServerStatusAttributes.put("nodeId", nodeId == null ? null : nodeId.toString()); - snapshotOfServerStatusAttributes.put("uptime", server.getUptime()); - snapshotOfServerStatusAttributes.put("state", server.getState().toString()); + if (instance.server != null) { + HashMap snapshotOfServerStatusAttributes = new HashMap<>(); + snapshotOfServerStatusAttributes.putAll(immutableStateValues); + snapshotOfServerStatusAttributes.put("identity", server.getIdentity()); + SimpleString nodeId = server.getNodeID(); + snapshotOfServerStatusAttributes.put("nodeId", nodeId == null ? null : nodeId.toString()); + snapshotOfServerStatusAttributes.put("uptime", server.getUptime()); + snapshotOfServerStatusAttributes.put("state", server.getState().toString()); - update("server", JsonUtil.toJsonObject(snapshotOfServerStatusAttributes)); + update(SERVER_COMPONENT, JsonUtil.toJsonObject(snapshotOfServerStatusAttributes)); + } } public synchronized void update(String component, String statusJson) { - JsonObjectBuilder jsonObjectBuilder = JsonLoader.createObjectBuilder(); - jsonObjectBuilder.add(component, JsonUtil.readJsonObject(statusJson)); - globalStatus = JsonUtil.mergeAndUpdate(globalStatus, jsonObjectBuilder.build()); + update(component, JsonUtil.readJsonObject(statusJson)); } - public synchronized void update(String component, JsonObject componentStatus) { - JsonObjectBuilder jsonObjectBuilder = JsonLoader.createObjectBuilder(); - jsonObjectBuilder.add(component, componentStatus); + public synchronized void update(String component, HashMap statusAttributes) { + update(component, JsonUtil.toJsonObject(statusAttributes)); + } + + public synchronized void update(String componentPath, JsonObject componentStatus) { + JsonObjectBuilder jsonObjectBuilder = JsonUtil.objectBuilderWithValueAtPath(componentPath, componentStatus); globalStatus = JsonUtil.mergeAndUpdate(globalStatus, jsonObjectBuilder.build()); } diff --git a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/ReloadableProperties.java b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/ReloadableProperties.java index 396340402c..a9864429e8 100644 --- a/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/ReloadableProperties.java +++ b/artemis-server/src/main/java/org/apache/activemq/artemis/spi/core/security/jaas/ReloadableProperties.java @@ -19,6 +19,7 @@ package org.apache.activemq.artemis.spi.core.security.jaas; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -30,9 +31,14 @@ import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import org.apache.activemq.artemis.core.server.ActiveMQServerLogger; +import org.apache.activemq.artemis.core.server.impl.ServerStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.invoke.MethodHandles; +import java.util.zip.Adler32; +import java.util.zip.Checksum; + +import static org.apache.activemq.artemis.core.server.impl.ServerStatus.JAAS_COMPONENT; public class ReloadableProperties { @@ -47,6 +53,8 @@ public class ReloadableProperties { private Map regexpProps; private long reloadTime = -1; private final PropertiesLoader.FileNameKey key; + private final Checksum adler32 = new Adler32(); + private long checksum = 0; public ReloadableProperties(PropertiesLoader.FileNameKey key) { this.key = key; @@ -58,9 +66,25 @@ public class ReloadableProperties { public synchronized ReloadableProperties obtained() { if (reloadTime < 0 || (key.isReload() && hasModificationAfter(reloadTime))) { - props = new Properties(); + + adler32.reset(); + props = new Properties() { + // want to checksum in read order + @Override + public synchronized Object put(Object key, Object value) { + String sKey = key instanceof String ? (String)key : null; + String sValue = value instanceof String ? (String)value : null; + if (sKey != null && sValue != null) { + adler32.update(sKey.getBytes(StandardCharsets.UTF_8)); + adler32.update('='); + adler32.update(sValue.getBytes(StandardCharsets.UTF_8)); + } + return super.put(key, value); + } + }; try { load(key.file(), props); + checksum = adler32.getValue(); invertedProps = null; invertedValueProps = null; regexpProps = null; @@ -74,10 +98,18 @@ public class ReloadableProperties { } } reloadTime = System.currentTimeMillis(); + updateStatus(); } return this; } + private void updateStatus() { + HashMap statusAttributes = new HashMap<>(); + statusAttributes.put("Alder32", String.valueOf(checksum)); + statusAttributes.put("reloadTime", String.valueOf(reloadTime)); + ServerStatus.getInstance().update(JAAS_COMPONENT + "/properties/" + key.file.getName(), statusAttributes); + } + public synchronized Map invertedPropertiesMap() { if (invertedProps == null) { invertedProps = new HashMap<>(props.size()); diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImplTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImplTest.java index fb10c84f66..1c35aa9eee 100644 --- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImplTest.java +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/config/impl/ConfigurationImplTest.java @@ -1337,6 +1337,7 @@ public class ConfigurationImplTest extends ActiveMQTestBase { assertTrue(jsonStatus.contains(UPDATED_SHA)); assertFalse(jsonStatus.contains(SHA)); + assertTrue(jsonStatus.contains("alder32")); } /** diff --git a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleTest.java b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleTest.java index 540c722183..1f5274388d 100644 --- a/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleTest.java +++ b/artemis-server/src/test/java/org/apache/activemq/artemis/core/security/jaas/PropertiesLoginModuleTest.java @@ -25,19 +25,25 @@ import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.FailedLoginException; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; +import java.io.BufferedReader; import java.io.File; +import java.io.FileReader; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; +import org.apache.activemq.artemis.core.server.impl.ServerStatus; import org.apache.activemq.artemis.spi.core.security.jaas.RolePrincipal; import org.apache.activemq.artemis.spi.core.security.jaas.UserPrincipal; import org.apache.commons.io.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.invoke.MethodHandles; +import java.util.zip.Adler32; +import java.util.zip.Checksum; + import org.junit.Assert; import org.junit.Test; @@ -104,6 +110,9 @@ public class PropertiesLoginModuleTest extends Assert { assertEquals("Should have one user principal", 1, subject.getPrincipals(UserPrincipal.class).size()); assertEquals("Should have two group principals", 2, subject.getPrincipals(RolePrincipal.class).size()); + String hashOriginal = genHash(usersFile); + assertTrue("Contains hash", ServerStatus.getInstance().asJson().contains(hashOriginal)); + context.logout(); assertEquals("Should have zero principals", 0, subject.getPrincipals().size()); @@ -127,9 +136,28 @@ public class PropertiesLoginModuleTest extends Assert { context.logout(); + String hashUpdated = genHash(usersFile); + assertTrue("Contains hashUpdated", ServerStatus.getInstance().asJson().contains(hashUpdated)); + assertNotEquals(hashOriginal, hashUpdated); + assertEquals("Should have zero principals", 0, subject.getPrincipals().size()); } + private String genHash(File usersFile) { + Checksum hash = new Adler32(); + + try (FileReader fileReader = new FileReader(usersFile); BufferedReader bufferedReader = new BufferedReader(fileReader)) { + String line = null; + while ((line = bufferedReader.readLine()) != null) { + if (!line.startsWith("#") && !line.isBlank()) { + hash.update(line.getBytes(StandardCharsets.UTF_8)); + } + } + } catch (Exception expectedEof) { + } + return String.valueOf(hash.getValue()); + } + @Test public void testBadUseridLogin() throws Exception { LoginContext context = new LoginContext("PropertiesLogin", new UserPassHandler("BAD", "secret"));